nbtree row comparison scan keys consist of one header key (which
appears in so->keyData[]), and 2 or more subkeys (which
_bt_check_rowcompare accesses by following a pointer stored in the
header key). This design makes sense to me, but it's not at all
obvious why the scan keys are structured in this way.
Attached patch adds comments about all this above
_bt_check_rowcompare. It also adds a couple of new documenting
assertions. This clears up why don't we just include the subkeys in
the main so->keyData[] array instead [1], and why it's useful to
sometimes treat a row compare inequality as if it was a similar scalar
inequality (on the most significant row member's index column).
I didn't understand every nuance of row compare inequalities myself
until quite recently. The rules with NULLs are particularly tricky.
It seems worthwhile to clear things up now in large part due to the
recent addition of code in places like _bt_advance_array_keys -- code
that wants to to treat row compare keys as if they were just a simple
scalar inequality on the row compare's most significant column. That
general behavior isn't new (e.g., _bt_first has long ignored row
compare scan key markings when deducing a NOT NULL constraint), but
it's not easy to see why it's correct.
I'd like to commit this on both the master branch and the 18 branch in
the next couple of days. Seems worth keeping them in sync for this.
[1] https://www.postgresql.org/message-id/24134.1137366192%40sss.pgh.pa.us
-- 
Peter Geoghegan