LCOV - code coverage report
Current view: top level - src/backend/utils/adt - mac.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 82.3 % 192 158
Test Date: 2026-03-04 03:14:50 Functions: 90.9 % 22 20
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * mac.c
       4              :  *    PostgreSQL type definitions for 6 byte, EUI-48, MAC addresses.
       5              :  *
       6              :  * Portions Copyright (c) 1998-2026, PostgreSQL Global Development Group
       7              :  *
       8              :  * IDENTIFICATION
       9              :  *        src/backend/utils/adt/mac.c
      10              :  *
      11              :  *-------------------------------------------------------------------------
      12              :  */
      13              : 
      14              : #include "postgres.h"
      15              : 
      16              : #include "common/hashfn.h"
      17              : #include "lib/hyperloglog.h"
      18              : #include "libpq/pqformat.h"
      19              : #include "port/pg_bswap.h"
      20              : #include "utils/fmgrprotos.h"
      21              : #include "utils/guc.h"
      22              : #include "utils/inet.h"
      23              : #include "utils/sortsupport.h"
      24              : 
      25              : 
      26              : /*
      27              :  *  Utility macros used for sorting and comparing:
      28              :  */
      29              : 
      30              : #define hibits(addr) \
      31              :   ((unsigned long)(((addr)->a<<16)|((addr)->b<<8)|((addr)->c)))
      32              : 
      33              : #define lobits(addr) \
      34              :   ((unsigned long)(((addr)->d<<16)|((addr)->e<<8)|((addr)->f)))
      35              : 
      36              : /* sortsupport for macaddr */
      37              : typedef struct
      38              : {
      39              :     int64       input_count;    /* number of non-null values seen */
      40              :     bool        estimating;     /* true if estimating cardinality */
      41              : 
      42              :     hyperLogLogState abbr_card; /* cardinality estimator */
      43              : } macaddr_sortsupport_state;
      44              : 
      45              : static int  macaddr_cmp_internal(macaddr *a1, macaddr *a2);
      46              : static int  macaddr_fast_cmp(Datum x, Datum y, SortSupport ssup);
      47              : static bool macaddr_abbrev_abort(int memtupcount, SortSupport ssup);
      48              : static Datum macaddr_abbrev_convert(Datum original, SortSupport ssup);
      49              : 
      50              : /*
      51              :  *  MAC address reader.  Accepts several common notations.
      52              :  */
      53              : 
      54              : Datum
      55         1864 : macaddr_in(PG_FUNCTION_ARGS)
      56              : {
      57         1864 :     char       *str = PG_GETARG_CSTRING(0);
      58         1864 :     Node       *escontext = fcinfo->context;
      59              :     macaddr    *result;
      60              :     int         a,
      61              :                 b,
      62              :                 c,
      63              :                 d,
      64              :                 e,
      65              :                 f;
      66              :     char        junk[2];
      67              :     int         count;
      68              : 
      69              :     /* %1s matches iff there is trailing non-whitespace garbage */
      70              : 
      71         1864 :     count = sscanf(str, "%x:%x:%x:%x:%x:%x%1s",
      72              :                    &a, &b, &c, &d, &e, &f, junk);
      73         1864 :     if (count != 6)
      74           36 :         count = sscanf(str, "%x-%x-%x-%x-%x-%x%1s",
      75              :                        &a, &b, &c, &d, &e, &f, junk);
      76         1864 :     if (count != 6)
      77           33 :         count = sscanf(str, "%2x%2x%2x:%2x%2x%2x%1s",
      78              :                        &a, &b, &c, &d, &e, &f, junk);
      79         1864 :     if (count != 6)
      80           30 :         count = sscanf(str, "%2x%2x%2x-%2x%2x%2x%1s",
      81              :                        &a, &b, &c, &d, &e, &f, junk);
      82         1864 :     if (count != 6)
      83           27 :         count = sscanf(str, "%2x%2x.%2x%2x.%2x%2x%1s",
      84              :                        &a, &b, &c, &d, &e, &f, junk);
      85         1864 :     if (count != 6)
      86           24 :         count = sscanf(str, "%2x%2x-%2x%2x-%2x%2x%1s",
      87              :                        &a, &b, &c, &d, &e, &f, junk);
      88         1864 :     if (count != 6)
      89           21 :         count = sscanf(str, "%2x%2x%2x%2x%2x%2x%1s",
      90              :                        &a, &b, &c, &d, &e, &f, junk);
      91         1864 :     if (count != 6)
      92           18 :         ereturn(escontext, (Datum) 0,
      93              :                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
      94              :                  errmsg("invalid input syntax for type %s: \"%s\"", "macaddr",
      95              :                         str)));
      96              : 
      97         1846 :     if ((a < 0) || (a > 255) || (b < 0) || (b > 255) ||
      98         1846 :         (c < 0) || (c > 255) || (d < 0) || (d > 255) ||
      99         1846 :         (e < 0) || (e > 255) || (f < 0) || (f > 255))
     100            0 :         ereturn(escontext, (Datum) 0,
     101              :                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     102              :                  errmsg("invalid octet value in \"macaddr\" value: \"%s\"", str)));
     103              : 
     104         1846 :     result = palloc_object(macaddr);
     105              : 
     106         1846 :     result->a = a;
     107         1846 :     result->b = b;
     108         1846 :     result->c = c;
     109         1846 :     result->d = d;
     110         1846 :     result->e = e;
     111         1846 :     result->f = f;
     112              : 
     113         1846 :     PG_RETURN_MACADDR_P(result);
     114              : }
     115              : 
     116              : /*
     117              :  *  MAC address output function.  Fixed format.
     118              :  */
     119              : 
     120              : Datum
     121         1844 : macaddr_out(PG_FUNCTION_ARGS)
     122              : {
     123         1844 :     macaddr    *addr = PG_GETARG_MACADDR_P(0);
     124              :     char       *result;
     125              : 
     126         1844 :     result = (char *) palloc(32);
     127              : 
     128         1844 :     snprintf(result, 32, "%02x:%02x:%02x:%02x:%02x:%02x",
     129         1844 :              addr->a, addr->b, addr->c, addr->d, addr->e, addr->f);
     130              : 
     131         1844 :     PG_RETURN_CSTRING(result);
     132              : }
     133              : 
     134              : /*
     135              :  *      macaddr_recv            - converts external binary format to macaddr
     136              :  *
     137              :  * The external representation is just the six bytes, MSB first.
     138              :  */
     139              : Datum
     140            0 : macaddr_recv(PG_FUNCTION_ARGS)
     141              : {
     142            0 :     StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
     143              :     macaddr    *addr;
     144              : 
     145            0 :     addr = palloc_object(macaddr);
     146              : 
     147            0 :     addr->a = pq_getmsgbyte(buf);
     148            0 :     addr->b = pq_getmsgbyte(buf);
     149            0 :     addr->c = pq_getmsgbyte(buf);
     150            0 :     addr->d = pq_getmsgbyte(buf);
     151            0 :     addr->e = pq_getmsgbyte(buf);
     152            0 :     addr->f = pq_getmsgbyte(buf);
     153              : 
     154            0 :     PG_RETURN_MACADDR_P(addr);
     155              : }
     156              : 
     157              : /*
     158              :  *      macaddr_send            - converts macaddr to binary format
     159              :  */
     160              : Datum
     161            0 : macaddr_send(PG_FUNCTION_ARGS)
     162              : {
     163            0 :     macaddr    *addr = PG_GETARG_MACADDR_P(0);
     164              :     StringInfoData buf;
     165              : 
     166            0 :     pq_begintypsend(&buf);
     167            0 :     pq_sendbyte(&buf, addr->a);
     168            0 :     pq_sendbyte(&buf, addr->b);
     169            0 :     pq_sendbyte(&buf, addr->c);
     170            0 :     pq_sendbyte(&buf, addr->d);
     171            0 :     pq_sendbyte(&buf, addr->e);
     172            0 :     pq_sendbyte(&buf, addr->f);
     173            0 :     PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
     174              : }
     175              : 
     176              : 
     177              : /*
     178              :  *  Comparison function for sorting:
     179              :  */
     180              : 
     181              : static int
     182        33564 : macaddr_cmp_internal(macaddr *a1, macaddr *a2)
     183              : {
     184        33564 :     if (hibits(a1) < hibits(a2))
     185        13926 :         return -1;
     186        19638 :     else if (hibits(a1) > hibits(a2))
     187        12357 :         return 1;
     188         7281 :     else if (lobits(a1) < lobits(a2))
     189           29 :         return -1;
     190         7252 :     else if (lobits(a1) > lobits(a2))
     191           25 :         return 1;
     192              :     else
     193         7227 :         return 0;
     194              : }
     195              : 
     196              : Datum
     197         7929 : macaddr_cmp(PG_FUNCTION_ARGS)
     198              : {
     199         7929 :     macaddr    *a1 = PG_GETARG_MACADDR_P(0);
     200         7929 :     macaddr    *a2 = PG_GETARG_MACADDR_P(1);
     201              : 
     202         7929 :     PG_RETURN_INT32(macaddr_cmp_internal(a1, a2));
     203              : }
     204              : 
     205              : /*
     206              :  *  Boolean comparisons.
     207              :  */
     208              : 
     209              : Datum
     210        10427 : macaddr_lt(PG_FUNCTION_ARGS)
     211              : {
     212        10427 :     macaddr    *a1 = PG_GETARG_MACADDR_P(0);
     213        10427 :     macaddr    *a2 = PG_GETARG_MACADDR_P(1);
     214              : 
     215        10427 :     PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) < 0);
     216              : }
     217              : 
     218              : Datum
     219         3118 : macaddr_le(PG_FUNCTION_ARGS)
     220              : {
     221         3118 :     macaddr    *a1 = PG_GETARG_MACADDR_P(0);
     222         3118 :     macaddr    *a2 = PG_GETARG_MACADDR_P(1);
     223              : 
     224         3118 :     PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) <= 0);
     225              : }
     226              : 
     227              : Datum
     228         3129 : macaddr_eq(PG_FUNCTION_ARGS)
     229              : {
     230         3129 :     macaddr    *a1 = PG_GETARG_MACADDR_P(0);
     231         3129 :     macaddr    *a2 = PG_GETARG_MACADDR_P(1);
     232              : 
     233         3129 :     PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) == 0);
     234              : }
     235              : 
     236              : Datum
     237         2457 : macaddr_ge(PG_FUNCTION_ARGS)
     238              : {
     239         2457 :     macaddr    *a1 = PG_GETARG_MACADDR_P(0);
     240         2457 :     macaddr    *a2 = PG_GETARG_MACADDR_P(1);
     241              : 
     242         2457 :     PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) >= 0);
     243              : }
     244              : 
     245              : Datum
     246         4928 : macaddr_gt(PG_FUNCTION_ARGS)
     247              : {
     248         4928 :     macaddr    *a1 = PG_GETARG_MACADDR_P(0);
     249         4928 :     macaddr    *a2 = PG_GETARG_MACADDR_P(1);
     250              : 
     251         4928 :     PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) > 0);
     252              : }
     253              : 
     254              : Datum
     255           12 : macaddr_ne(PG_FUNCTION_ARGS)
     256              : {
     257           12 :     macaddr    *a1 = PG_GETARG_MACADDR_P(0);
     258           12 :     macaddr    *a2 = PG_GETARG_MACADDR_P(1);
     259              : 
     260           12 :     PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) != 0);
     261              : }
     262              : 
     263              : /*
     264              :  * Support function for hash indexes on macaddr.
     265              :  */
     266              : Datum
     267         1169 : hashmacaddr(PG_FUNCTION_ARGS)
     268              : {
     269         1169 :     macaddr    *key = PG_GETARG_MACADDR_P(0);
     270              : 
     271         1169 :     return hash_any((unsigned char *) key, sizeof(macaddr));
     272              : }
     273              : 
     274              : Datum
     275           30 : hashmacaddrextended(PG_FUNCTION_ARGS)
     276              : {
     277           30 :     macaddr    *key = PG_GETARG_MACADDR_P(0);
     278              : 
     279           30 :     return hash_any_extended((unsigned char *) key, sizeof(macaddr),
     280           30 :                              PG_GETARG_INT64(1));
     281              : }
     282              : 
     283              : /*
     284              :  * Arithmetic functions: bitwise NOT, AND, OR.
     285              :  */
     286              : Datum
     287           36 : macaddr_not(PG_FUNCTION_ARGS)
     288              : {
     289           36 :     macaddr    *addr = PG_GETARG_MACADDR_P(0);
     290              :     macaddr    *result;
     291              : 
     292           36 :     result = palloc_object(macaddr);
     293           36 :     result->a = ~addr->a;
     294           36 :     result->b = ~addr->b;
     295           36 :     result->c = ~addr->c;
     296           36 :     result->d = ~addr->d;
     297           36 :     result->e = ~addr->e;
     298           36 :     result->f = ~addr->f;
     299           36 :     PG_RETURN_MACADDR_P(result);
     300              : }
     301              : 
     302              : Datum
     303           36 : macaddr_and(PG_FUNCTION_ARGS)
     304              : {
     305           36 :     macaddr    *addr1 = PG_GETARG_MACADDR_P(0);
     306           36 :     macaddr    *addr2 = PG_GETARG_MACADDR_P(1);
     307              :     macaddr    *result;
     308              : 
     309           36 :     result = palloc_object(macaddr);
     310           36 :     result->a = addr1->a & addr2->a;
     311           36 :     result->b = addr1->b & addr2->b;
     312           36 :     result->c = addr1->c & addr2->c;
     313           36 :     result->d = addr1->d & addr2->d;
     314           36 :     result->e = addr1->e & addr2->e;
     315           36 :     result->f = addr1->f & addr2->f;
     316           36 :     PG_RETURN_MACADDR_P(result);
     317              : }
     318              : 
     319              : Datum
     320           36 : macaddr_or(PG_FUNCTION_ARGS)
     321              : {
     322           36 :     macaddr    *addr1 = PG_GETARG_MACADDR_P(0);
     323           36 :     macaddr    *addr2 = PG_GETARG_MACADDR_P(1);
     324              :     macaddr    *result;
     325              : 
     326           36 :     result = palloc_object(macaddr);
     327           36 :     result->a = addr1->a | addr2->a;
     328           36 :     result->b = addr1->b | addr2->b;
     329           36 :     result->c = addr1->c | addr2->c;
     330           36 :     result->d = addr1->d | addr2->d;
     331           36 :     result->e = addr1->e | addr2->e;
     332           36 :     result->f = addr1->f | addr2->f;
     333           36 :     PG_RETURN_MACADDR_P(result);
     334              : }
     335              : 
     336              : /*
     337              :  *  Truncation function to allow comparing mac manufacturers.
     338              :  *  From suggestion by Alex Pilosov <alex@pilosoft.com>
     339              :  */
     340              : Datum
     341           36 : macaddr_trunc(PG_FUNCTION_ARGS)
     342              : {
     343           36 :     macaddr    *addr = PG_GETARG_MACADDR_P(0);
     344              :     macaddr    *result;
     345              : 
     346           36 :     result = palloc_object(macaddr);
     347              : 
     348           36 :     result->a = addr->a;
     349           36 :     result->b = addr->b;
     350           36 :     result->c = addr->c;
     351           36 :     result->d = 0;
     352           36 :     result->e = 0;
     353           36 :     result->f = 0;
     354              : 
     355           36 :     PG_RETURN_MACADDR_P(result);
     356              : }
     357              : 
     358              : /*
     359              :  * SortSupport strategy function. Populates a SortSupport struct with the
     360              :  * information necessary to use comparison by abbreviated keys.
     361              :  */
     362              : Datum
     363           12 : macaddr_sortsupport(PG_FUNCTION_ARGS)
     364              : {
     365           12 :     SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0);
     366              : 
     367           12 :     ssup->comparator = macaddr_fast_cmp;
     368           12 :     ssup->ssup_extra = NULL;
     369              : 
     370           12 :     if (ssup->abbreviate)
     371              :     {
     372              :         macaddr_sortsupport_state *uss;
     373              :         MemoryContext oldcontext;
     374              : 
     375           10 :         oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt);
     376              : 
     377           10 :         uss = palloc_object(macaddr_sortsupport_state);
     378           10 :         uss->input_count = 0;
     379           10 :         uss->estimating = true;
     380           10 :         initHyperLogLog(&uss->abbr_card, 10);
     381              : 
     382           10 :         ssup->ssup_extra = uss;
     383              : 
     384           10 :         ssup->comparator = ssup_datum_unsigned_cmp;
     385           10 :         ssup->abbrev_converter = macaddr_abbrev_convert;
     386           10 :         ssup->abbrev_abort = macaddr_abbrev_abort;
     387           10 :         ssup->abbrev_full_comparator = macaddr_fast_cmp;
     388              : 
     389           10 :         MemoryContextSwitchTo(oldcontext);
     390              :     }
     391              : 
     392           12 :     PG_RETURN_VOID();
     393              : }
     394              : 
     395              : /*
     396              :  * SortSupport "traditional" comparison function. Pulls two MAC addresses from
     397              :  * the heap and runs a standard comparison on them.
     398              :  */
     399              : static int
     400         1564 : macaddr_fast_cmp(Datum x, Datum y, SortSupport ssup)
     401              : {
     402         1564 :     macaddr    *arg1 = DatumGetMacaddrP(x);
     403         1564 :     macaddr    *arg2 = DatumGetMacaddrP(y);
     404              : 
     405         1564 :     return macaddr_cmp_internal(arg1, arg2);
     406              : }
     407              : 
     408              : /*
     409              :  * Callback for estimating effectiveness of abbreviated key optimization.
     410              :  *
     411              :  * We pay no attention to the cardinality of the non-abbreviated data, because
     412              :  * there is no equality fast-path within authoritative macaddr comparator.
     413              :  */
     414              : static bool
     415            6 : macaddr_abbrev_abort(int memtupcount, SortSupport ssup)
     416              : {
     417            6 :     macaddr_sortsupport_state *uss = ssup->ssup_extra;
     418              :     double      abbr_card;
     419              : 
     420            6 :     if (memtupcount < 10000 || uss->input_count < 10000 || !uss->estimating)
     421            6 :         return false;
     422              : 
     423            0 :     abbr_card = estimateHyperLogLog(&uss->abbr_card);
     424              : 
     425              :     /*
     426              :      * If we have >100k distinct values, then even if we were sorting many
     427              :      * billion rows we'd likely still break even, and the penalty of undoing
     428              :      * that many rows of abbrevs would probably not be worth it. At this point
     429              :      * we stop counting because we know that we're now fully committed.
     430              :      */
     431            0 :     if (abbr_card > 100000.0)
     432              :     {
     433            0 :         if (trace_sort)
     434            0 :             elog(LOG,
     435              :                  "macaddr_abbrev: estimation ends at cardinality %f"
     436              :                  " after " INT64_FORMAT " values (%d rows)",
     437              :                  abbr_card, uss->input_count, memtupcount);
     438            0 :         uss->estimating = false;
     439            0 :         return false;
     440              :     }
     441              : 
     442              :     /*
     443              :      * Target minimum cardinality is 1 per ~2k of non-null inputs. 0.5 row
     444              :      * fudge factor allows us to abort earlier on genuinely pathological data
     445              :      * where we've had exactly one abbreviated value in the first 2k
     446              :      * (non-null) rows.
     447              :      */
     448            0 :     if (abbr_card < uss->input_count / 2000.0 + 0.5)
     449              :     {
     450            0 :         if (trace_sort)
     451            0 :             elog(LOG,
     452              :                  "macaddr_abbrev: aborting abbreviation at cardinality %f"
     453              :                  " below threshold %f after " INT64_FORMAT " values (%d rows)",
     454              :                  abbr_card, uss->input_count / 2000.0 + 0.5, uss->input_count,
     455              :                  memtupcount);
     456            0 :         return true;
     457              :     }
     458              : 
     459            0 :     if (trace_sort)
     460            0 :         elog(LOG,
     461              :              "macaddr_abbrev: cardinality %f after " INT64_FORMAT
     462              :              " values (%d rows)", abbr_card, uss->input_count, memtupcount);
     463              : 
     464            0 :     return false;
     465              : }
     466              : 
     467              : /*
     468              :  * SortSupport conversion routine. Converts original macaddr representation
     469              :  * to abbreviated key representation.
     470              :  *
     471              :  * Packs the bytes of a 6-byte MAC address into a Datum and treats it as an
     472              :  * unsigned integer for purposes of comparison. On a 64-bit machine, there
     473              :  * will be two zeroed bytes of padding. The integer is converted to native
     474              :  * endianness to facilitate easy comparison.
     475              :  */
     476              : static Datum
     477           84 : macaddr_abbrev_convert(Datum original, SortSupport ssup)
     478              : {
     479           84 :     macaddr_sortsupport_state *uss = ssup->ssup_extra;
     480           84 :     macaddr    *authoritative = DatumGetMacaddrP(original);
     481              :     Datum       res;
     482              : 
     483              :     /*
     484              :      * Zero out the 8-byte Datum and copy in the 6 bytes of the MAC address.
     485              :      * There will be two bytes of zero padding on the end of the least
     486              :      * significant bits.
     487              :      */
     488              :     StaticAssertDecl(sizeof(res) >= sizeof(macaddr),
     489              :                      "Datum is too small for macaddr");
     490           84 :     memset(&res, 0, sizeof(res));
     491           84 :     memcpy(&res, authoritative, sizeof(macaddr));
     492           84 :     uss->input_count += 1;
     493              : 
     494              :     /*
     495              :      * Cardinality estimation. The estimate uses uint32, so XOR the two 32-bit
     496              :      * halves together to produce slightly more entropy. The two zeroed bytes
     497              :      * won't have any practical impact on this operation.
     498              :      */
     499           84 :     if (uss->estimating)
     500              :     {
     501              :         uint32      tmp;
     502              : 
     503           84 :         tmp = DatumGetUInt32(res) ^ (uint32) (DatumGetUInt64(res) >> 32);
     504              : 
     505           84 :         addHyperLogLog(&uss->abbr_card, DatumGetUInt32(hash_uint32(tmp)));
     506              :     }
     507              : 
     508              :     /*
     509              :      * Byteswap on little-endian machines.
     510              :      *
     511              :      * This is needed so that ssup_datum_unsigned_cmp() (an unsigned integer
     512              :      * 3-way comparator) works correctly on all platforms. Without this, the
     513              :      * comparator would have to call memcmp() with a pair of pointers to the
     514              :      * first byte of each abbreviated key, which is slower.
     515              :      */
     516           84 :     res = DatumBigEndianToNative(res);
     517              : 
     518           84 :     return res;
     519              : }
        

Generated by: LCOV version 2.0-1