diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c index 622bb88..cd185b8 100644 --- a/src/backend/utils/adt/rowtypes.c +++ b/src/backend/utils/adt/rowtypes.c @@ -25,6 +25,7 @@ #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/typcache.h" +#include "utils/varbit.h" /* @@ -1818,3 +1819,79 @@ btrecordimagecmp(PG_FUNCTION_ARGS) { PG_RETURN_INT32(record_image_cmp(fcinfo)); } + +/* + * record_nulls: return null map as bit + */ +Datum +record_nulls(PG_FUNCTION_ARGS) +{ + HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0); + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tuple; + int ncolumns, + natts, + len, /* varlena length of result */ + attno; + bool hasnulls; + bits8 *bp; /* ptr to null bitmap in tuple */ + bits8 x = 0; + VarBit *result; /* The resulting bit string */ + bits8 *r; /* pointer into the result */ + + /* Extract type info from the tuple itself */ + tupType = HeapTupleHeaderGetTypeId(rec); + tupTypmod = HeapTupleHeaderGetTypMod(rec); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + natts = tupdesc->natts; + + /* Build a temporary HeapTuple control structure */ + /* XXX there's probably a cheaper way to do this... */ + tuple.t_len = HeapTupleHeaderGetDatumLength(rec); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = rec; + hasnulls = HeapTupleHasNulls(&tuple); + + bp = rec->t_bits; /* ptr to null bitmap in tuple */ + + /* Find how many columns are dropped */ + ncolumns = natts; + if (hasnulls) + for (attno = 0; attno < natts; attno++) + if (tupdesc->attrs[attno]->attisdropped) + ncolumns--; + + len = VARBITTOTALLEN(ncolumns); + /* set to 0 so that *r is always initialised and string is zero-padded */ + result = (VarBit *) palloc0(len); + SET_VARSIZE(result, len); + VARBITLEN(result) = ncolumns; + + /* If there are no NULLs then we're done */ + if (hasnulls) + { + r = VARBITS(result); + x = HIGHBIT; + for (attno = 0; attno < natts; attno++) + { + /* Ignore dropped columns in datatype */ + if (tupdesc->attrs[attno]->attisdropped) + continue; + + if (att_isnull(attno, bp)) + *r |= x; + x >>= 1; + if (x == 0) + { + x = HIGHBIT; + r++; + } + } + } + + ReleaseTupleDesc(tupdesc); + PG_RETURN_VARBIT_P(result); +} diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index e2d08ba..092d5f4 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4949,6 +4949,10 @@ DATA(insert OID = 3186 ( record_image_ge PGNSP PGUID 12 1 0 0 0 f f f f t f DATA(insert OID = 3187 ( btrecordimagecmp PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 23 "2249 2249" _null_ _null_ _null_ _null_ _null_ btrecordimagecmp _null_ _null_ _null_ )); DESCR("less-equal-greater based on byte images"); +/* misc record functions */ +DATA(insert OID = 3343 ( record_nulls PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 1560 "2249" _null_ _null_ _null_ _null_ _null_ record_nulls _null_ _null_ _null_ )); +DESCR("bitmap of fields in a record that are NULL"); + /* Extensions */ DATA(insert OID = 3082 ( pg_available_extensions PGNSP PGUID 12 10 100 0 0 f f f f t t s s 0 0 2249 "" "{19,25,25}" "{o,o,o}" "{name,default_version,comment}" _null_ _null_ pg_available_extensions _null_ _null_ _null_ )); DESCR("list available extensions"); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 2ae212a..2b75658 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -718,6 +718,7 @@ extern Datum record_image_gt(PG_FUNCTION_ARGS); extern Datum record_image_le(PG_FUNCTION_ARGS); extern Datum record_image_ge(PG_FUNCTION_ARGS); extern Datum btrecordimagecmp(PG_FUNCTION_ARGS); +extern Datum record_nulls(PG_FUNCTION_ARGS); /* ruleutils.c */ extern bool quote_all_identifiers; diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql index a62dee2..d187205 100644 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@ -310,3 +310,14 @@ with r(a,b) as (values (1,row(1,2)), (1,row(null,null)), (1,null), (null,row(1,2)), (null,row(null,null)), (null,null) ) select r, r is null as isnull, r is not null as isnotnull from r; + +-- +-- Test record_nulls() +-- +/* Currenly doesn't work because record_nulls only works with registered types +select record_nulls(r), expected, CASE WHEN record_nulls(r) <> expected THEN 'BAD' END AS bad, r +from (values(row(NULL,NULL,NULL,2,2,NULL,2,2), '11100100'::varbit), + (row(2),'0') + ) v(r,expected) +; +*/