agora inbox for [email protected]  
help / color / mirror / Atom feed
Crash in BRIN minmax-multi indexes
275+ messages / 4 participants
[nested] [flat]

* Crash in BRIN minmax-multi indexes
@ 2021-03-31 17:29  Jaime Casanova <[email protected]>
  0 siblings, 1 reply; 275+ messages in thread

From: Jaime Casanova @ 2021-03-31 17:29 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>; Tomas Vondra <[email protected]>

Hi,

Just found $SUBJECT involving time with time zone and a subselect. I
still don't have narrowed to the exact table/index minimal schema but
if you run this query on the regression database it will creash.

```
update public.brintest_multi set
  timetzcol = (select tz from generate_series('2021-01-01'::timestamp
with time zone, '2021-01-31', '5 days') tz limit 1)
;
```

attached a backtrace. Let me know if you need extra information.

--
Jaime Casanova
Director de Servicios Profesionales
SYSTEMGUARDS - Consultores de PostgreSQL

#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
        set = {__val = {4194304, 140736618859920, 2, 6, 6570493, 93847671406576, 4611686018427388799, 140140941466278, 0, 
            281470681751456, 0, 0, 0, 0, 0, 0}}
        pid = <optimized out>
        tid = <optimized out>
        ret = <optimized out>
#1  0x00007f751ad08535 in __GI_abort () at abort.c:79
        save_stage = 1
        act = {__sigaction_handler = {sa_handler = 0x0, sa_sigaction = 0x0}, sa_mask = {__val = {0, 0, 0, 0, 0, 140140939223029, 
              2, 7004563297194213396, 7003431879952131129, 93847671406576, 7003772732868633408, 0, 18123550304647508992, 
              140736618860160, 93847715200152, 140736618861024}}, sa_flags = -1658978320, sa_restorer = 0x555a9fba3c98}
        sigs = {__val = {32, 0 <repeats 15 times>}}
#2  0x0000555a9d872557 in ExceptionalCondition (conditionName=0x555a9d8ec6c3 "delta >= 0", 
    errorType=0x555a9d8ebf74 "FailedAssertion", fileName=0x555a9d8ebf60 "brin_minmax_multi.c", lineNumber=2095) at assert.c:69
No locals.
#3  0x0000555a9d1e9933 in brin_minmax_multi_distance_timetz (fcinfo=0x7fffcc2c9250) at brin_minmax_multi.c:2095
        delta = -6785000000
        ta = 0x555a9fb99720
        tb = 0x555a9fb9c768
#4  0x0000555a9d87c6f1 in FunctionCall2Coll (flinfo=0x555a9fbd7c88, collation=0, arg1=93847715157792, arg2=93847715170152)
    at fmgr.c:1163
        fcinfodata = {fcinfo = {flinfo = 0x555a9fbd7c88, context = 0x0, resultinfo = 0x0, fncollation = 0, isnull = false, 
            nargs = 2, args = 0x7fffcc2c9270}, 
          fcinfo_data = "\210|\275\237ZU", '\000' <repeats 24 times>, "\002\000 \227\271\237ZU\000\000\000\000\000\000\000\000\000\000hǹ\237ZU\000\000\000\000\000@\317#\270A"}
        fcinfo = 0x7fffcc2c9250
        result = 4735574381219020800
        __func__ = "FunctionCall2Coll"
#5  0x0000555a9d1e85b7 in build_distances (distanceFn=0x555a9fbd7c88, colloid=0, eranges=0x555a9fba3b50, neranges=10)
    at brin_minmax_multi.c:1350
        a1 = 93847715157792
        a2 = 93847715170152
        r = 4735574381219020800
        i = 4
        ndistances = 9
        distances = 0x555a9fba3c68
#6  0x0000555a9d1e912f in compactify_ranges (bdesc=0x555a9fbd72a8, ranges=0x555a9fc02df8, max_values=32)
    at brin_minmax_multi.c:1820
        cmpFn = 0x555a9fbd7cc0
        distanceFn = 0x555a9fbd7c88
        eranges = 0x555a9fba3b50
        neranges = 10
        distances = 0x555a00000010
        ctx = 0x555a9fba3a30
        oldctx = 0x555a9fb9b9f0
#7  0x0000555a9d1ea409 in brin_minmax_multi_serialize (bdesc=0x555a9fbd72a8, src=93847715589624, dst=0x555a9fb9bea8)
    at brin_minmax_multi.c:2333
        ranges = 0x555a9fc02df8
        s = 0x555a9fb9d688
#8  0x0000555a9d1f0601 in brin_form_tuple (brdesc=0x555a9fbd72a8, blkno=0, tuple=0x555a9fb9bb10, size=0x7fffcc2c94e8)
    at brin_tuple.c:165
        datumno = 1
        values = 0x555a9fb9c108
        nulls = 0x555a9fb9c920
        anynulls = true
        rettuple = 0x89fc97d00
        keyno = 15
        idxattno = 15
        phony_infomask = 0
        phony_nullbitmap = 0x555a9fb9c9a0 "\177\177\177~\177\177\177\177"
        len = 93847671475524
        hoff = 2048
        data_len = 93847715170728
        i = 32767
        untoasted_values = 0x555a9fb9c220
        nuntoasted = 0
#9  0x0000555a9d1e09c1 in brininsert (idxRel=0x555a9fc97dd0, values=0x7fffcc2c9640, nulls=0x7fffcc2c9620, heaptid=0x555a9fb92ea8, 
    heapRel=0x555a9fc83540, checkUnique=UNIQUE_CHECK_NO, indexUnchanged=false, indexInfo=0x555a9fbac900) at brin.c:281
        lp = 0x7f751a1abfb8
        origsz = 1592
        newsz = 93847674317391
        page = 0x7f751a1abf80 "\004"
        origtup = 0x555a9fb9c9c0
        newtup = 0x7fffcc2c9520
        samepage = false
        need_insert = true
        off = 9
        brtup = 0x7f751a1ac1d8
        dtup = 0x555a9fb9bb10
        pagesPerRange = 1
        origHeapBlk = 0
        heapBlk = 0
        bdesc = 0x555a9fbd72a8
        revmap = 0x555a9fb93248
        buf = 16301
        tupcxt = 0x555a9fb9b9f0
        oldcxt = 0x555a9fbafa80
        autosummarize = false
        __func__ = "brininsert"
#10 0x0000555a9d280a1e in index_insert (indexRelation=0x555a9fc97dd0, values=0x7fffcc2c9640, isnull=0x7fffcc2c9620, 
    heap_t_ctid=0x555a9fb92ea8, heapRelation=0x555a9fc83540, checkUnique=UNIQUE_CHECK_NO, indexUnchanged=false, 
    indexInfo=0x555a9fbac900) at indexam.c:193
        __func__ = "index_insert"
#11 0x0000555a9d493a2f in ExecInsertIndexTuples (resultRelInfo=0x555a9fbac470, slot=0x555a9fb92e78, estate=0x555a9fbafba0, 
    update=true, noDupErr=false, specConflict=0x0, arbiterIndexes=0x0) at execIndexing.c:416
        applyNoDupErr = false
        checkUnique = UNIQUE_CHECK_NO
        indexRelation = 0x555a9fc97dd0
        indexInfo = 0x555a9fbac900
        indexUnchanged = false
        satisfiesConstraint = false
        tupleid = 0x555a9fb92ea8
        result = 0x0
        i = 0
        numIndices = 1
        relationDescs = 0x555a9fbacf88
        heapRelation = 0x555a9fc83540
        indexInfoArray = 0x555a9fbacfa8
        econtext = 0x555a9fb939c0
        values = {1005713280000, 800, 800, 8800, 93847715133420, 1065353216, 0, 93847715133440, 93847715133446, 93847715133453, 
          7200, 19630000000, 18446083844818551616, 18445887828109551616, 93847715133488, 93847715133504, 93847715133516, 
          93847715133521, 100352, 0, 140736618862400, 93847671962456, 140736618862640, 140736618862636, 0, 93847714641432, 1, 
          93847715131000, 140736618862834, 93847716115776, 12136208, 93847715133352}
        isnull = {false <repeats 24 times>, 212, 55, 185, 159, 90, 85, false, false}
#12 0x0000555a9d4d8d50 in ExecUpdate (mtstate=0x555a9fbb17b0, resultRelInfo=0x555a9fbac470, tupleid=0x7fffcc2c98f2, oldtuple=0x0, 
    slot=0x555a9fb92e78, planSlot=0x555a9fbad710, epqstate=0x555a9fbb18a8, estate=0x555a9fbafba0, canSetTag=true)
    at nodeModifyTable.c:1634
        lockmode = LockTupleNoKeyExclusive
        partition_constraint_failed = false
        update_indexes = true
        resultRelationDesc = 0x555a9fc83540
        result = TM_Ok
        tmfd = {ctid = {ip_blkid = {bi_hi = 55056, bi_lo = 40890}, ip_posid = 21850}, xmax = 2679713920, cmax = 21850, 
          traversed = 40}
        recheckIndexes = 0x0
        __func__ = "ExecUpdate"
#13 0x0000555a9d4d9dc9 in ExecModifyTable (pstate=0x555a9fbb17b0) at nodeModifyTable.c:2254
        node = 0x555a9fbb17b0
        estate = 0x555a9fbafba0
        operation = CMD_UPDATE
        resultRelInfo = 0x555a9fbac470
        subplanstate = 0x555a9fbacfc8
        junkfilter = 0x555a9fb93080
        slot = 0x555a9fb92e78
        planSlot = 0x555a9fbad710
        tupleid = 0x7fffcc2c98f2
        tuple_ctid = {ip_blkid = {bi_hi = 0, bi_lo = 0}, ip_posid = 9}
        oldtupdata = {t_len = 3425474800, t_self = {ip_blkid = {bi_hi = 32767, bi_lo = 0}, ip_posid = 5870}, t_tableOid = 21850, 
          t_data = 0x555a9fb930d8}
        oldtuple = 0x0
        proute = 0x0
        relinfos = 0x0
        lc = 0xe300000001
        __func__ = "ExecModifyTable"
#14 0x0000555a9d4a1007 in ExecProcNodeFirst (node=0x555a9fbb17b0) at execProcnode.c:456
No locals.
#15 0x0000555a9d49541b in ExecProcNode (node=0x555a9fbb17b0) at ../../../src/include/executor/executor.h:247
No locals.
#16 0x0000555a9d497d77 in ExecutePlan (estate=0x555a9fbafba0, planstate=0x555a9fbb17b0, use_parallel_mode=false, 
    operation=CMD_UPDATE, sendTuples=false, numberTuples=0, direction=ForwardScanDirection, dest=0x555a9fbe7e48, 
    execute_once=true) at execMain.c:1528
        slot = 0x555a9fbafba0
        current_tuple_count = 0
#17 0x0000555a9d495a7c in standard_ExecutorRun (queryDesc=0x555a9fbd9590, direction=ForwardScanDirection, count=0, 
    execute_once=true) at execMain.c:350
        estate = 0x555a9fbafba0
        operation = CMD_UPDATE
        dest = 0x555a9fbe7e48
        sendTuples = false
        oldcontext = 0x555a9fbd9470
        __func__ = "standard_ExecutorRun"
#18 0x0000555a9d495892 in ExecutorRun (queryDesc=0x555a9fbd9590, direction=ForwardScanDirection, count=0, execute_once=true)
    at execMain.c:294
No locals.
#19 0x0000555a9d6e1d85 in ProcessQuery (plan=0x555a9fc70d58, 
    sourceText=0x555a9fa988e0 "update public.brintest_multi set \n  timetzcol = (select tz from generate_series('2021-01-01'::timestamp with time zone, '2021-01-31', '5 days') tz limit 1 offset 4)\n;", params=0x0, queryEnv=0x0, dest=0x555a9fbe7e48, 
    qc=0x7fffcc2c9d10) at pquery.c:160
        queryDesc = 0x555a9fbd9590
#20 0x0000555a9d6e3742 in PortalRunMulti (portal=0x555a9fb039a0, isTopLevel=true, setHoldSnapshot=false, dest=0x555a9fbe7e48, 
    altdest=0x555a9fbe7e48, qc=0x7fffcc2c9d10) at pquery.c:1267
        pstmt = 0x555a9fc70d58
        stmtlist_item__state = {l = 0x555a9fbe7df0, i = 0}
        active_snapshot_set = true
        stmtlist_item = 0x555a9fbe7e08
#21 0x0000555a9d6e2cc4 in PortalRun (portal=0x555a9fb039a0, count=9223372036854775807, isTopLevel=true, run_once=true, 
    dest=0x555a9fbe7e48, altdest=0x555a9fbe7e48, qc=0x7fffcc2c9d10) at pquery.c:779
        _save_exception_stack = 0x7fffcc2c9e20
        _save_context_stack = 0x0
        _local_sigjmp_buf = {{__jmpbuf = {0, 6890411692784566327, 93847671406576, 140736618865472, 0, 0, 6890411692744720439, 
              780541552607576119}, __mask_was_saved = 0, __saved_mask = {__val = {19822817785, 93847715479088, 128, 
                93847714105280, 93847714552240, 140736618863728, 64880, 93847678360869, 93847715478432, 140736618863760, 
                93847678539968, 13, 112, 93847715479224, 112, 3425475744}}}}
        _do_rethrow = false
        result = false
        nprocessed = 93847715479112
        saveTopTransactionResourceOwner = 0x555a9fac95c0
        saveTopTransactionContext = 0x555a9fb1b4c0
        saveActivePortal = 0x0
        saveResourceOwner = 0x555a9fac95c0
        savePortalContext = 0x0
        saveMemoryContext = 0x555a9fb1b4c0
        __func__ = "PortalRun"
#22 0x0000555a9d6dc616 in exec_simple_query (
    query_string=0x555a9fa988e0 "update public.brintest_multi set \n  timetzcol = (select tz from generate_series('2021-01-01'::timestamp with time zone, '2021-01-31', '5 days') tz limit 1 offset 4)\n;") at postgres.c:1184
        snapshot_set = true
        per_parsetree_context = 0x0
        plantree_list = 0x555a9fbe7df0
        parsetree = 0x555a9fa99f68
        commandTag = CMDTAG_UPDATE
        qc = {commandTag = CMDTAG_UNKNOWN, nprocessed = 0}
        querytree_list = 0x555a9fbc29c8
        portal = 0x555a9fb039a0
        receiver = 0x555a9fbe7e48
        format = 0
        parsetree_item__state = {l = 0x555a9fa99fa0, i = 0}
        dest = DestRemote
        oldcontext = 0x555a9fb1b4c0
        parsetree_list = 0x555a9fa99fa0
        parsetree_item = 0x555a9fa99fb8
        save_log_statement_stats = false
        was_logged = false
        use_implicit_block = false
        msec_str = "\340\210\251\237ZU\000\000\246\000\000\000\006\000\000\000`\235,\314\377\177\000\000\020\336P\235\246\000\000"
        __func__ = "exec_simple_query"
#23 0x0000555a9d6e0d3f in PostgresMain (argc=1, argv=0x7fffcc2c9fa0, dbname=0x555a9fac7880 "regression", 
    username=0x555a9fac7858 "jcasanov") at postgres.c:4414
        query_string = 0x555a9fa988e0 "update public.brintest_multi set \n  timetzcol = (select tz from generate_series('2021-01-01'::timestamp with time zone, '2021-01-31', '5 days') tz limit 1 offset 4)\n;"
        firstchar = 81
        input_message = {
          data = 0x555a9fa988e0 "update public.brintest_multi set \n  timetzcol = (select tz from generate_series('2021-01-01'::timestamp with time zone, '2021-01-31', '5 days') tz limit 1 offset 4)\n;", len = 167, maxlen = 1024, cursor = 167}
        local_sigjmp_buf = {{__jmpbuf = {0, 6890411692878938167, 93847671406576, 140736618865472, 0, 0, 6890411692824412215, 
              780541553613684791}, __mask_was_saved = 1, __saved_mask = {__val = {4194304, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
                1099511627520, 0, 0, 4294901760, 18446462598732840960, 0}}}}
        send_ready_for_query = false
        idle_in_transaction_timeout_enabled = false
        idle_session_timeout_enabled = false
        __func__ = "PostgresMain"
#24 0x0000555a9d618dbe in BackendRun (port=0x555a9fabe790) at postmaster.c:4483
        av = {0x555a9da19267 "postgres", 0x0}
        ac = 1
#25 0x0000555a9d6186ed in BackendStartup (port=0x555a9fabe790) at postmaster.c:4205
        bn = 0x555a9fabb940
        pid = 0
        __func__ = "BackendStartup"
#26 0x0000555a9d614a84 in ServerLoop () at postmaster.c:1737
        port = 0x555a9fabe790
        i = 2
        rmask = {fds_bits = {128, 0 <repeats 15 times>}}
        selres = 1
        now = 1617209413
        readmask = {fds_bits = {224, 0 <repeats 15 times>}}
        nSockets = 8
        last_lockfile_recheck_time = 1617209412
        last_touch_time = 1617209114
        __func__ = "ServerLoop"
#27 0x0000555a9d6142d3 in PostmasterMain (argc=3, argv=0x555a9fa91ee0) at postmaster.c:1409
        opt = -1
        status = 0
        userDoption = 0x555a9fab57c0 "data"
        listen_addr_saved = true
        i = 64
        output_config_variable = 0x0
        __func__ = "PostmasterMain"
#28 0x0000555a9d510f67 in main (argc=3, argv=0x555a9fa91ee0) at main.c:209
        do_check_root = true


Attachments:

  [text/plain] minmax_multi_crash.txt (13.1K, ../../CAJKUy5jLZFLCxyxfT=MfK5mtPfSzHA1rVLowR-j4RRsFVvKm7A@mail.gmail.com/2-minmax_multi_crash.txt)
  download | inline:
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
        set = {__val = {4194304, 140736618859920, 2, 6, 6570493, 93847671406576, 4611686018427388799, 140140941466278, 0, 
            281470681751456, 0, 0, 0, 0, 0, 0}}
        pid = <optimized out>
        tid = <optimized out>
        ret = <optimized out>
#1  0x00007f751ad08535 in __GI_abort () at abort.c:79
        save_stage = 1
        act = {__sigaction_handler = {sa_handler = 0x0, sa_sigaction = 0x0}, sa_mask = {__val = {0, 0, 0, 0, 0, 140140939223029, 
              2, 7004563297194213396, 7003431879952131129, 93847671406576, 7003772732868633408, 0, 18123550304647508992, 
              140736618860160, 93847715200152, 140736618861024}}, sa_flags = -1658978320, sa_restorer = 0x555a9fba3c98}
        sigs = {__val = {32, 0 <repeats 15 times>}}
#2  0x0000555a9d872557 in ExceptionalCondition (conditionName=0x555a9d8ec6c3 "delta >= 0", 
    errorType=0x555a9d8ebf74 "FailedAssertion", fileName=0x555a9d8ebf60 "brin_minmax_multi.c", lineNumber=2095) at assert.c:69
No locals.
#3  0x0000555a9d1e9933 in brin_minmax_multi_distance_timetz (fcinfo=0x7fffcc2c9250) at brin_minmax_multi.c:2095
        delta = -6785000000
        ta = 0x555a9fb99720
        tb = 0x555a9fb9c768
#4  0x0000555a9d87c6f1 in FunctionCall2Coll (flinfo=0x555a9fbd7c88, collation=0, arg1=93847715157792, arg2=93847715170152)
    at fmgr.c:1163
        fcinfodata = {fcinfo = {flinfo = 0x555a9fbd7c88, context = 0x0, resultinfo = 0x0, fncollation = 0, isnull = false, 
            nargs = 2, args = 0x7fffcc2c9270}, 
          fcinfo_data = "\210|\275\237ZU", '\000' <repeats 24 times>, "\002\000 \227\271\237ZU\000\000\000\000\000\000\000\000\000\000hǹ\237ZU\000\000\000\000\000@\317#\270A"}
        fcinfo = 0x7fffcc2c9250
        result = 4735574381219020800
        __func__ = "FunctionCall2Coll"
#5  0x0000555a9d1e85b7 in build_distances (distanceFn=0x555a9fbd7c88, colloid=0, eranges=0x555a9fba3b50, neranges=10)
    at brin_minmax_multi.c:1350
        a1 = 93847715157792
        a2 = 93847715170152
        r = 4735574381219020800
        i = 4
        ndistances = 9
        distances = 0x555a9fba3c68
#6  0x0000555a9d1e912f in compactify_ranges (bdesc=0x555a9fbd72a8, ranges=0x555a9fc02df8, max_values=32)
    at brin_minmax_multi.c:1820
        cmpFn = 0x555a9fbd7cc0
        distanceFn = 0x555a9fbd7c88
        eranges = 0x555a9fba3b50
        neranges = 10
        distances = 0x555a00000010
        ctx = 0x555a9fba3a30
        oldctx = 0x555a9fb9b9f0
#7  0x0000555a9d1ea409 in brin_minmax_multi_serialize (bdesc=0x555a9fbd72a8, src=93847715589624, dst=0x555a9fb9bea8)
    at brin_minmax_multi.c:2333
        ranges = 0x555a9fc02df8
        s = 0x555a9fb9d688
#8  0x0000555a9d1f0601 in brin_form_tuple (brdesc=0x555a9fbd72a8, blkno=0, tuple=0x555a9fb9bb10, size=0x7fffcc2c94e8)
    at brin_tuple.c:165
        datumno = 1
        values = 0x555a9fb9c108
        nulls = 0x555a9fb9c920
        anynulls = true
        rettuple = 0x89fc97d00
        keyno = 15
        idxattno = 15
        phony_infomask = 0
        phony_nullbitmap = 0x555a9fb9c9a0 "\177\177\177~\177\177\177\177"
        len = 93847671475524
        hoff = 2048
        data_len = 93847715170728
        i = 32767
        untoasted_values = 0x555a9fb9c220
        nuntoasted = 0
#9  0x0000555a9d1e09c1 in brininsert (idxRel=0x555a9fc97dd0, values=0x7fffcc2c9640, nulls=0x7fffcc2c9620, heaptid=0x555a9fb92ea8, 
    heapRel=0x555a9fc83540, checkUnique=UNIQUE_CHECK_NO, indexUnchanged=false, indexInfo=0x555a9fbac900) at brin.c:281
        lp = 0x7f751a1abfb8
        origsz = 1592
        newsz = 93847674317391
        page = 0x7f751a1abf80 "\004"
        origtup = 0x555a9fb9c9c0
        newtup = 0x7fffcc2c9520
        samepage = false
        need_insert = true
        off = 9
        brtup = 0x7f751a1ac1d8
        dtup = 0x555a9fb9bb10
        pagesPerRange = 1
        origHeapBlk = 0
        heapBlk = 0
        bdesc = 0x555a9fbd72a8
        revmap = 0x555a9fb93248
        buf = 16301
        tupcxt = 0x555a9fb9b9f0
        oldcxt = 0x555a9fbafa80
        autosummarize = false
        __func__ = "brininsert"
#10 0x0000555a9d280a1e in index_insert (indexRelation=0x555a9fc97dd0, values=0x7fffcc2c9640, isnull=0x7fffcc2c9620, 
    heap_t_ctid=0x555a9fb92ea8, heapRelation=0x555a9fc83540, checkUnique=UNIQUE_CHECK_NO, indexUnchanged=false, 
    indexInfo=0x555a9fbac900) at indexam.c:193
        __func__ = "index_insert"
#11 0x0000555a9d493a2f in ExecInsertIndexTuples (resultRelInfo=0x555a9fbac470, slot=0x555a9fb92e78, estate=0x555a9fbafba0, 
    update=true, noDupErr=false, specConflict=0x0, arbiterIndexes=0x0) at execIndexing.c:416
        applyNoDupErr = false
        checkUnique = UNIQUE_CHECK_NO
        indexRelation = 0x555a9fc97dd0
        indexInfo = 0x555a9fbac900
        indexUnchanged = false
        satisfiesConstraint = false
        tupleid = 0x555a9fb92ea8
        result = 0x0
        i = 0
        numIndices = 1
        relationDescs = 0x555a9fbacf88
        heapRelation = 0x555a9fc83540
        indexInfoArray = 0x555a9fbacfa8
        econtext = 0x555a9fb939c0
        values = {1005713280000, 800, 800, 8800, 93847715133420, 1065353216, 0, 93847715133440, 93847715133446, 93847715133453, 
          7200, 19630000000, 18446083844818551616, 18445887828109551616, 93847715133488, 93847715133504, 93847715133516, 
          93847715133521, 100352, 0, 140736618862400, 93847671962456, 140736618862640, 140736618862636, 0, 93847714641432, 1, 
          93847715131000, 140736618862834, 93847716115776, 12136208, 93847715133352}
        isnull = {false <repeats 24 times>, 212, 55, 185, 159, 90, 85, false, false}
#12 0x0000555a9d4d8d50 in ExecUpdate (mtstate=0x555a9fbb17b0, resultRelInfo=0x555a9fbac470, tupleid=0x7fffcc2c98f2, oldtuple=0x0, 
    slot=0x555a9fb92e78, planSlot=0x555a9fbad710, epqstate=0x555a9fbb18a8, estate=0x555a9fbafba0, canSetTag=true)
    at nodeModifyTable.c:1634
        lockmode = LockTupleNoKeyExclusive
        partition_constraint_failed = false
        update_indexes = true
        resultRelationDesc = 0x555a9fc83540
        result = TM_Ok
        tmfd = {ctid = {ip_blkid = {bi_hi = 55056, bi_lo = 40890}, ip_posid = 21850}, xmax = 2679713920, cmax = 21850, 
          traversed = 40}
        recheckIndexes = 0x0
        __func__ = "ExecUpdate"
#13 0x0000555a9d4d9dc9 in ExecModifyTable (pstate=0x555a9fbb17b0) at nodeModifyTable.c:2254
        node = 0x555a9fbb17b0
        estate = 0x555a9fbafba0
        operation = CMD_UPDATE
        resultRelInfo = 0x555a9fbac470
        subplanstate = 0x555a9fbacfc8
        junkfilter = 0x555a9fb93080
        slot = 0x555a9fb92e78
        planSlot = 0x555a9fbad710
        tupleid = 0x7fffcc2c98f2
        tuple_ctid = {ip_blkid = {bi_hi = 0, bi_lo = 0}, ip_posid = 9}
        oldtupdata = {t_len = 3425474800, t_self = {ip_blkid = {bi_hi = 32767, bi_lo = 0}, ip_posid = 5870}, t_tableOid = 21850, 
          t_data = 0x555a9fb930d8}
        oldtuple = 0x0
        proute = 0x0
        relinfos = 0x0
        lc = 0xe300000001
        __func__ = "ExecModifyTable"
#14 0x0000555a9d4a1007 in ExecProcNodeFirst (node=0x555a9fbb17b0) at execProcnode.c:456
No locals.
#15 0x0000555a9d49541b in ExecProcNode (node=0x555a9fbb17b0) at ../../../src/include/executor/executor.h:247
No locals.
#16 0x0000555a9d497d77 in ExecutePlan (estate=0x555a9fbafba0, planstate=0x555a9fbb17b0, use_parallel_mode=false, 
    operation=CMD_UPDATE, sendTuples=false, numberTuples=0, direction=ForwardScanDirection, dest=0x555a9fbe7e48, 
    execute_once=true) at execMain.c:1528
        slot = 0x555a9fbafba0
        current_tuple_count = 0
#17 0x0000555a9d495a7c in standard_ExecutorRun (queryDesc=0x555a9fbd9590, direction=ForwardScanDirection, count=0, 
    execute_once=true) at execMain.c:350
        estate = 0x555a9fbafba0
        operation = CMD_UPDATE
        dest = 0x555a9fbe7e48
        sendTuples = false
        oldcontext = 0x555a9fbd9470
        __func__ = "standard_ExecutorRun"
#18 0x0000555a9d495892 in ExecutorRun (queryDesc=0x555a9fbd9590, direction=ForwardScanDirection, count=0, execute_once=true)
    at execMain.c:294
No locals.
#19 0x0000555a9d6e1d85 in ProcessQuery (plan=0x555a9fc70d58, 
    sourceText=0x555a9fa988e0 "update public.brintest_multi set \n  timetzcol = (select tz from generate_series('2021-01-01'::timestamp with time zone, '2021-01-31', '5 days') tz limit 1 offset 4)\n;", params=0x0, queryEnv=0x0, dest=0x555a9fbe7e48, 
    qc=0x7fffcc2c9d10) at pquery.c:160
        queryDesc = 0x555a9fbd9590
#20 0x0000555a9d6e3742 in PortalRunMulti (portal=0x555a9fb039a0, isTopLevel=true, setHoldSnapshot=false, dest=0x555a9fbe7e48, 
    altdest=0x555a9fbe7e48, qc=0x7fffcc2c9d10) at pquery.c:1267
        pstmt = 0x555a9fc70d58
        stmtlist_item__state = {l = 0x555a9fbe7df0, i = 0}
        active_snapshot_set = true
        stmtlist_item = 0x555a9fbe7e08
#21 0x0000555a9d6e2cc4 in PortalRun (portal=0x555a9fb039a0, count=9223372036854775807, isTopLevel=true, run_once=true, 
    dest=0x555a9fbe7e48, altdest=0x555a9fbe7e48, qc=0x7fffcc2c9d10) at pquery.c:779
        _save_exception_stack = 0x7fffcc2c9e20
        _save_context_stack = 0x0
        _local_sigjmp_buf = {{__jmpbuf = {0, 6890411692784566327, 93847671406576, 140736618865472, 0, 0, 6890411692744720439, 
              780541552607576119}, __mask_was_saved = 0, __saved_mask = {__val = {19822817785, 93847715479088, 128, 
                93847714105280, 93847714552240, 140736618863728, 64880, 93847678360869, 93847715478432, 140736618863760, 
                93847678539968, 13, 112, 93847715479224, 112, 3425475744}}}}
        _do_rethrow = false
        result = false
        nprocessed = 93847715479112
        saveTopTransactionResourceOwner = 0x555a9fac95c0
        saveTopTransactionContext = 0x555a9fb1b4c0
        saveActivePortal = 0x0
        saveResourceOwner = 0x555a9fac95c0
        savePortalContext = 0x0
        saveMemoryContext = 0x555a9fb1b4c0
        __func__ = "PortalRun"
#22 0x0000555a9d6dc616 in exec_simple_query (
    query_string=0x555a9fa988e0 "update public.brintest_multi set \n  timetzcol = (select tz from generate_series('2021-01-01'::timestamp with time zone, '2021-01-31', '5 days') tz limit 1 offset 4)\n;") at postgres.c:1184
        snapshot_set = true
        per_parsetree_context = 0x0
        plantree_list = 0x555a9fbe7df0
        parsetree = 0x555a9fa99f68
        commandTag = CMDTAG_UPDATE
        qc = {commandTag = CMDTAG_UNKNOWN, nprocessed = 0}
        querytree_list = 0x555a9fbc29c8
        portal = 0x555a9fb039a0
        receiver = 0x555a9fbe7e48
        format = 0
        parsetree_item__state = {l = 0x555a9fa99fa0, i = 0}
        dest = DestRemote
        oldcontext = 0x555a9fb1b4c0
        parsetree_list = 0x555a9fa99fa0
        parsetree_item = 0x555a9fa99fb8
        save_log_statement_stats = false
        was_logged = false
        use_implicit_block = false
        msec_str = "\340\210\251\237ZU\000\000\246\000\000\000\006\000\000\000`\235,\314\377\177\000\000\020\336P\235\246\000\000"
        __func__ = "exec_simple_query"
#23 0x0000555a9d6e0d3f in PostgresMain (argc=1, argv=0x7fffcc2c9fa0, dbname=0x555a9fac7880 "regression", 
    username=0x555a9fac7858 "jcasanov") at postgres.c:4414
        query_string = 0x555a9fa988e0 "update public.brintest_multi set \n  timetzcol = (select tz from generate_series('2021-01-01'::timestamp with time zone, '2021-01-31', '5 days') tz limit 1 offset 4)\n;"
        firstchar = 81
        input_message = {
          data = 0x555a9fa988e0 "update public.brintest_multi set \n  timetzcol = (select tz from generate_series('2021-01-01'::timestamp with time zone, '2021-01-31', '5 days') tz limit 1 offset 4)\n;", len = 167, maxlen = 1024, cursor = 167}
        local_sigjmp_buf = {{__jmpbuf = {0, 6890411692878938167, 93847671406576, 140736618865472, 0, 0, 6890411692824412215, 
              780541553613684791}, __mask_was_saved = 1, __saved_mask = {__val = {4194304, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
                1099511627520, 0, 0, 4294901760, 18446462598732840960, 0}}}}
        send_ready_for_query = false
        idle_in_transaction_timeout_enabled = false
        idle_session_timeout_enabled = false
        __func__ = "PostgresMain"
#24 0x0000555a9d618dbe in BackendRun (port=0x555a9fabe790) at postmaster.c:4483
        av = {0x555a9da19267 "postgres", 0x0}
        ac = 1
#25 0x0000555a9d6186ed in BackendStartup (port=0x555a9fabe790) at postmaster.c:4205
        bn = 0x555a9fabb940
        pid = 0
        __func__ = "BackendStartup"
#26 0x0000555a9d614a84 in ServerLoop () at postmaster.c:1737
        port = 0x555a9fabe790
        i = 2
        rmask = {fds_bits = {128, 0 <repeats 15 times>}}
        selres = 1
        now = 1617209413
        readmask = {fds_bits = {224, 0 <repeats 15 times>}}
        nSockets = 8
        last_lockfile_recheck_time = 1617209412
        last_touch_time = 1617209114
        __func__ = "ServerLoop"
#27 0x0000555a9d6142d3 in PostmasterMain (argc=3, argv=0x555a9fa91ee0) at postmaster.c:1409
        opt = -1
        status = 0
        userDoption = 0x555a9fab57c0 "data"
        listen_addr_saved = true
        i = 64
        output_config_variable = 0x0
        __func__ = "PostmasterMain"
#28 0x0000555a9d510f67 in main (argc=3, argv=0x555a9fa91ee0) at main.c:209
        do_check_root = true

^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* Re: Crash in BRIN minmax-multi indexes
@ 2021-03-31 18:20  Zhihong Yu <[email protected]>
  parent: Jaime Casanova <[email protected]>
  0 siblings, 1 reply; 275+ messages in thread

From: Zhihong Yu @ 2021-03-31 18:20 UTC (permalink / raw)
  To: Jaime Casanova <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>; Tomas Vondra <[email protected]>

Hi,
In build_distances():

        a1 = eranges[i].maxval;
        a2 = eranges[i + 1].minval;

It seems there was overlap between the successive ranges, leading to
delta = -6785000000

FYI

On Wed, Mar 31, 2021 at 10:30 AM Jaime Casanova <
[email protected]> wrote:

> Hi,
>
> Just found $SUBJECT involving time with time zone and a subselect. I
> still don't have narrowed to the exact table/index minimal schema but
> if you run this query on the regression database it will creash.
>
> ```
> update public.brintest_multi set
>   timetzcol = (select tz from generate_series('2021-01-01'::timestamp
> with time zone, '2021-01-31', '5 days') tz limit 1)
> ;
> ```
>
> attached a backtrace. Let me know if you need extra information.
>
> --
> Jaime Casanova
> Director de Servicios Profesionales
> SYSTEMGUARDS - Consultores de PostgreSQL
>


^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* Re: Crash in BRIN minmax-multi indexes
@ 2021-03-31 18:27  Tomas Vondra <[email protected]>
  parent: Zhihong Yu <[email protected]>
  0 siblings, 1 reply; 275+ messages in thread

From: Tomas Vondra @ 2021-03-31 18:27 UTC (permalink / raw)
  To: Zhihong Yu <[email protected]>; Jaime Casanova <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On 3/31/21 8:20 PM, Zhihong Yu wrote:
> Hi,
> In build_distances():
> 
>         a1 = eranges[i].maxval;
>         a2 = eranges[i + 1].minval;
> 
> It seems there was overlap between the successive ranges, leading to
> delta = -6785000000
> 

I've been unable to reproduce this, so far :-( How exactly did you
manage to reproduce it?


The thing is - how could there be an overlap? The way we build expanded
ranges that should not be possible, I think. Can you print the ranges at
the end of fill_expanded_ranges? That should shed some light on this.


FWIW I suspect those asserts on delta may be a bit problematic due to
rounding errors. And I found one issue in the inet distance function,
because apparently

test=# select '10.2.14.243/24'::inet < '10.2.14.231/24'::inet;
 ?column?
----------
 f
(1 row)

but the delta formula currently ignores the mask. But that's a separate
issue.


regards

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company





^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* Re: Crash in BRIN minmax-multi indexes
@ 2021-03-31 22:25  Tomas Vondra <[email protected]>
  parent: Tomas Vondra <[email protected]>
  0 siblings, 2 replies; 275+ messages in thread

From: Tomas Vondra @ 2021-03-31 22:25 UTC (permalink / raw)
  To: Zhihong Yu <[email protected]>; Jaime Casanova <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

Hi,

I think I found the issue - it's kinda obvious, really. We need to
consider the timezone, because the "time" parts alone may be sorted
differently. The attached patch should fix this, and it also fixes a
similar issue in the inet data type.

As for why the regression tests did not catch this, it's most likely
because the data is likely generated in "nice" ordering, or something
like that. I'll see if I can tweak the ordering to trigger these issues
reliably, and I'll do a bit more randomized testing.

There's also the question of rounding errors, which I think might cause
random assert failures (but in practice it's harmless, in the worst case
we'll merge the ranges a bit differently).


regards

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


Attachments:

  [text/x-patch] minmax-fixes.patch (2.0K, ../../[email protected]/2-minmax-fixes.patch)
  download | inline diff:
diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c
index 70109960e8..439e0b414c 100644
--- a/src/backend/access/brin/brin_minmax_multi.c
+++ b/src/backend/access/brin/brin_minmax_multi.c
@@ -2090,7 +2090,7 @@ brin_minmax_multi_distance_timetz(PG_FUNCTION_ARGS)
 	TimeTzADT  *ta = PG_GETARG_TIMETZADT_P(0);
 	TimeTzADT  *tb = PG_GETARG_TIMETZADT_P(1);
 
-	delta = tb->time - ta->time;
+	delta = (tb->time + tb->zone * USECS_PER_SEC) - (ta->time + ta->zone * USECS_PER_SEC);
 
 	Assert(delta >= 0);
 
@@ -2292,6 +2292,9 @@ brin_minmax_multi_distance_inet(PG_FUNCTION_ARGS)
 	inet	   *ipa = PG_GETARG_INET_PP(0);
 	inet	   *ipb = PG_GETARG_INET_PP(1);
 
+	int			lena,
+				lenb;
+
 	/*
 	 * If the addresses are from different families, consider them to be in
 	 * maximal possible distance (which is 1.0).
@@ -2299,20 +2302,38 @@ brin_minmax_multi_distance_inet(PG_FUNCTION_ARGS)
 	if (ip_family(ipa) != ip_family(ipb))
 		PG_RETURN_FLOAT8(1.0);
 
-	/* ipv4 or ipv6 */
-	if (ip_family(ipa) == PGSQL_AF_INET)
-		len = 4;
-	else
-		len = 16;				/* NS_IN6ADDRSZ */
-
 	addra = ip_addr(ipa);
 	addrb = ip_addr(ipb);
 
+	/*
+	 * The length is calculated from the mask length, because we sort the
+	 * addresses by first address in the range, so A.B.C.D/24 < A.B.C.1
+	 * (the first range starts at A.B.C.0, which is before A.B.C.1). We
+	 * don't want to produce negative delta in this case, so we just cut
+	 * the extra bytes.
+	 *
+	 * XXX Maybe this should be a bit more careful and cut the bits, not
+	 * just whole bytes.
+	 */
+	lena = ip_bits(ipa) / 8;
+	lenb = ip_bits(ipb) / 8;
+
+	len = Max(lena, lenb);
+
 	delta = 0;
 	for (i = len - 1; i >= 0; i--)
 	{
-		delta += (float8) addrb[i] - (float8) addra[i];
-		delta /= 256;
+		unsigned char a = addra[i];
+		unsigned char b = addrb[i];
+
+		if (i >= lena)
+			a = 0;
+
+		if (i >= lenb)
+			b = 0;
+
+		delta += (float8) b - (float8) a;
+		delta /= 255;
 	}
 
 	Assert((delta >= 0) && (delta <= 1));


^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* Re: Crash in BRIN minmax-multi indexes
@ 2021-03-31 22:53  Zhihong Yu <[email protected]>
  parent: Tomas Vondra <[email protected]>
  1 sibling, 1 reply; 275+ messages in thread

From: Zhihong Yu @ 2021-03-31 22:53 UTC (permalink / raw)
  To: Tomas Vondra <[email protected]>; +Cc: Jaime Casanova <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi,
For inet data type fix:

+       unsigned char a = addra[i];
+       unsigned char b = addrb[i];
+
+       if (i >= lena)
+           a = 0;
+
+       if (i >= lenb)
+           b = 0;

Should the length check precede the addra[i] ?
Something like:

       unsigned char a;
       if (i >= lena) a = 0;
       else a = addra[i];

Cheers

On Wed, Mar 31, 2021 at 3:25 PM Tomas Vondra <[email protected]>
wrote:

> Hi,
>
> I think I found the issue - it's kinda obvious, really. We need to
> consider the timezone, because the "time" parts alone may be sorted
> differently. The attached patch should fix this, and it also fixes a
> similar issue in the inet data type.
>
> As for why the regression tests did not catch this, it's most likely
> because the data is likely generated in "nice" ordering, or something
> like that. I'll see if I can tweak the ordering to trigger these issues
> reliably, and I'll do a bit more randomized testing.
>
> There's also the question of rounding errors, which I think might cause
> random assert failures (but in practice it's harmless, in the worst case
> we'll merge the ranges a bit differently).
>
>
> regards
>
> --
> Tomas Vondra
> EnterpriseDB: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* Re: Crash in BRIN minmax-multi indexes
@ 2021-03-31 23:10  Tomas Vondra <[email protected]>
  parent: Zhihong Yu <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Tomas Vondra @ 2021-03-31 23:10 UTC (permalink / raw)
  To: Zhihong Yu <[email protected]>; +Cc: Jaime Casanova <[email protected]>; PostgreSQL Hackers <[email protected]>

On 4/1/21 12:53 AM, Zhihong Yu wrote:
> Hi,
> For inet data type fix:
> 
> +       unsigned char a = addra[i];
> +       unsigned char b = addrb[i];
> +
> +       if (i >= lena)
> +           a = 0;
> +
> +       if (i >= lenb)
> +           b = 0;
> 
> Should the length check precede the addra[i] ?
> Something like:
> 
>        unsigned char a;
>        if (i >= lena) a = 0;
>        else a = addra[i];
> 

I don't think that makes any difference. We know the bytes are there, we
just want to ignore / reset them in some cases.


regards

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company





^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* Re: Crash in BRIN minmax-multi indexes
@ 2021-03-31 23:19  Jaime Casanova <[email protected]>
  parent: Tomas Vondra <[email protected]>
  1 sibling, 0 replies; 275+ messages in thread

From: Jaime Casanova @ 2021-03-31 23:19 UTC (permalink / raw)
  To: Tomas Vondra <[email protected]>; +Cc: Zhihong Yu <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Mar 31, 2021 at 5:25 PM Tomas Vondra
<[email protected]> wrote:
>
> Hi,
>
> I think I found the issue - it's kinda obvious, really. We need to
> consider the timezone, because the "time" parts alone may be sorted
> differently. The attached patch should fix this, and it also fixes a
> similar issue in the inet data type.
>

ah! yeah! obvious... if you say so ;)

> As for why the regression tests did not catch this, it's most likely
> because the data is likely generated in "nice" ordering, or something
> like that. I'll see if I can tweak the ordering to trigger these issues
> reliably, and I'll do a bit more randomized testing.
>
> There's also the question of rounding errors, which I think might cause
> random assert failures (but in practice it's harmless, in the worst case
> we'll merge the ranges a bit differently).
>
>

I can confirm this fixes the crash in the query I showed and the original case.

-- 
Jaime Casanova
Director de Servicios Profesionales
SYSTEMGUARDS - Consultores de PostgreSQL





^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread

* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12  Nikolay Shaplov <[email protected]>
  0 siblings, 0 replies; 275+ messages in thread

From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)

Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
 src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
 src/backend/commands/vacuum.c          |   4 +-
 src/include/access/reloptions.h        |  26 ++---
 src/include/c.h                        |  16 +++
 src/include/utils/rel.h                |   3 +-
 5 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
  *
  * To add an option:
  *
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
  * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
 		},
 		false
 	},
-	{
-		{
-			"vacuum_truncate",
-			"Enables vacuum to truncate empty pages at the end of this table",
-			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
-			ShareUpdateExclusiveLock
-		},
-		true
-	},
 	{
 		{
 			"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
 	{{NULL}}
 };
 
+static relopt_trenary trenaryRelOpts[] =
+{
+	{
+		{
+			"vacuum_truncate",
+			"Enables vacuum to truncate empty pages at the end of this table",
+			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+			ShareUpdateExclusiveLock
+		},
+		TRENARY_UNSET
+	},
+	/* list terminator */
+	{{NULL}}
+};
+
 static relopt_int intRelOpts[] =
 {
 	{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
 								   boolRelOpts[i].gen.lockmode));
 		j++;
 	}
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+								   trenaryRelOpts[i].gen.lockmode));
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
 		j++;
 	}
 
+	for (i = 0; trenaryRelOpts[i].gen.name; i++)
+	{
+		relOpts[j] = &trenaryRelOpts[i].gen;
+		relOpts[j]->type = RELOPT_TYPE_TRENARY;
+		relOpts[j]->namelen = strlen(relOpts[j]->name);
+		j++;
+	}
+
 	for (i = 0; intRelOpts[i].gen.name; i++)
 	{
 		relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
 		case RELOPT_TYPE_BOOL:
 			size = sizeof(relopt_bool);
 			break;
+		case RELOPT_TYPE_TRENARY:
+			size = sizeof(relopt_trenary);
+			break;
 		case RELOPT_TYPE_INT:
 			size = sizeof(relopt_int);
 			break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
 	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
 }
 
+/*
+ * init_trenary_reloption
+ *		Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+					trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption;
+
+	newoption = (relopt_trenary *) allocate_reloption(kinds,
+									RELOPT_TYPE_TRENARY, name, desc, lockmode);
+	newoption->default_val = default_val;
+
+	return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ *		Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+				   trenary default_val, LOCKMODE lockmode)
+{
+	relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+												 default_val, lockmode);
+
+	add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ *		Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+							const char *desc, trenary default_val,
+							int offset)
+{
+	relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+												name, desc,
+												default_val, 0);
+
+	add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
 
 /*
  * init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
 									option->gen->name, value)));
 			}
 			break;
+		case RELOPT_TYPE_TRENARY:
+			{
+				bool b;
+				parsed = parse_bool(value, &b);
+				option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+				if (validate && !parsed)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("invalid value for trenary option \"%s\": %s",
+									option->gen->name, value)));
+			}
+			break;
 		case RELOPT_TYPE_INT:
 			{
 				relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
 				char	   *itempos = ((char *) rdopts) + elems[j].offset;
 				char	   *string_val;
 
-				/*
-				 * If isset_offset is provided, store whether the reloption is
-				 * set there.
-				 */
-				if (elems[j].isset_offset > 0)
-				{
-					char	   *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
-					*(bool *) setpos = options[i].isset;
-				}
-
 				switch (options[i].gen->type)
 				{
 					case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
 							options[i].values.bool_val :
 							((relopt_bool *) options[i].gen)->default_val;
 						break;
+					case RELOPT_TYPE_TRENARY:
+						*(trenary *) itempos = options[i].isset ?
+							options[i].values.trenary_val :
+							((relopt_trenary *) options[i].gen)->default_val;
+						break;
 					case RELOPT_TYPE_INT:
 						*(int *) itempos = options[i].isset ?
 							options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 		offsetof(StdRdOptions, parallel_workers)},
 		{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
 		offsetof(StdRdOptions, vacuum_index_cleanup)},
-		{"vacuum_truncate", RELOPT_TYPE_BOOL,
-		offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+		{"vacuum_truncate", RELOPT_TYPE_TRENARY,
+		offsetof(StdRdOptions, vacuum_truncate)},
 		{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
 		offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
 	};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
 		elems[i].optname = opt->option->name;
 		elems[i].opttype = opt->option->type;
 		elems[i].offset = opt->offset;
-		elems[i].isset_offset = 0;	/* not supported for local relopts yet */
 
 		i++;
 	}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	{
 		StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
 
-		if (opts && opts->vacuum_truncate_set)
+		if (opts && opts->vacuum_truncate != TRENARY_UNSET)
 		{
-			if (opts->vacuum_truncate)
+			if (opts->vacuum_truncate == TRENARY_TRUE)
 				params.truncate = VACOPTVALUE_ENABLED;
 			else
 				params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
 typedef enum relopt_type
 {
 	RELOPT_TYPE_BOOL,
+	RELOPT_TYPE_TRENARY,  /* on, off, unset */
 	RELOPT_TYPE_INT,
 	RELOPT_TYPE_REAL,
 	RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
 	union
 	{
 		bool		bool_val;
+		trenary		trenary_val;
 		int			int_val;
 		double		real_val;
 		int			enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
 	bool		default_val;
 } relopt_bool;
 
+typedef struct relopt_rernary
+{
+	relopt_gen	gen;
+	int 		default_val;
+} relopt_trenary;
+
 typedef struct relopt_int
 {
 	relopt_gen	gen;
@@ -152,19 +160,6 @@ typedef struct
 	const char *optname;		/* option's name */
 	relopt_type opttype;		/* option's datatype */
 	int			offset;			/* offset of field in result struct */
-
-	/*
-	 * isset_offset is an optional offset of a field in the result struct that
-	 * stores whether the option is explicitly set for the relation or if it
-	 * just picked up the default value.  In most cases, this can be
-	 * accomplished by giving the reloption a special out-of-range default
-	 * value (e.g., some integer reloptions use -2), but this isn't always
-	 * possible.  For example, a Boolean reloption cannot be given an
-	 * out-of-range default, so we need another way to discover the source of
-	 * its value.  This offset is only used if given a value greater than
-	 * zero.
-	 */
-	int			isset_offset;
 } relopt_parse_elt;
 
 /* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
 extern relopt_kind add_reloption_kind(void);
 extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
 							   bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+					const char *desc, int default_val, LOCKMODE lockmode);
 extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
 							  int default_val, int min_val, int max_val,
 							  LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
 extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
 									 const char *desc, bool default_val,
 									 int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+								const char *name, const char *desc,
+								trenary default_val, 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/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
 
 #include <stdbool.h>
 
+/*
+ * trenary
+ *		Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+	TRENARY_FALSE = 0,
+	TRENARY_TRUE = 1,
+	TRENARY_UNSET = -1
+} trenary;
+
 
 /* ----------------------------------------------------------------
  *				Section 3:	standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
 	bool		user_catalog_table; /* use as an additional catalog relation */
 	int			parallel_workers;	/* max number of parallel workers */
 	StdRdOptIndexCleanup vacuum_index_cleanup;	/* controls index vacuuming */
-	bool		vacuum_truncate;	/* enables vacuum to truncate a relation */
-	bool		vacuum_truncate_set;	/* whether vacuum_truncate is set */
+	trenary		vacuum_truncate;	/* enables vacuum to truncate a relation */
 
 	/*
 	 * Fraction of pages in a relation that vacuum can eagerly scan and fail
-- 
2.39.2


--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
 filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
 name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"



^ permalink  raw  reply  [nested|flat] 275+ messages in thread


end of thread, other threads:[~2025-08-29 14:12 UTC | newest]

Thread overview: 275+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2021-03-31 17:29 Crash in BRIN minmax-multi indexes Jaime Casanova <[email protected]>
2021-03-31 18:20 ` Zhihong Yu <[email protected]>
2021-03-31 18:27   ` Tomas Vondra <[email protected]>
2021-03-31 22:25     ` Tomas Vondra <[email protected]>
2021-03-31 22:53       ` Zhihong Yu <[email protected]>
2021-03-31 23:10         ` Tomas Vondra <[email protected]>
2021-03-31 23:19       ` Jaime Casanova <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox