|
26 | 26 | #include "storage/indexfsm.h" |
27 | 27 | #include "utils/builtins.h" |
28 | 28 | #include "utils/index_selfuncs.h" |
| 29 | +#include "utils/memutils.h" |
29 | 30 | #include "utils/rel.h" |
30 | 31 | #include "utils/typcache.h" |
31 | 32 |
|
@@ -78,6 +79,7 @@ ginhandler(PG_FUNCTION_ARGS) |
78 | 79 | amroutine->amproperty = NULL; |
79 | 80 | amroutine->ambuildphasename = ginbuildphasename; |
80 | 81 | amroutine->amvalidate = ginvalidate; |
| 82 | + amroutine->amcomparedatums = gincomparedatums; |
81 | 83 | amroutine->amadjustmembers = ginadjustmembers; |
82 | 84 | amroutine->ambeginscan = ginbeginscan; |
83 | 85 | amroutine->amrescan = ginrescan; |
@@ -477,13 +479,6 @@ cmpEntries(const void *a, const void *b, void *arg) |
477 | 479 | return res; |
478 | 480 | } |
479 | 481 |
|
480 | | - |
481 | | -/* |
482 | | - * Extract the index key values from an indexable item |
483 | | - * |
484 | | - * The resulting key values are sorted, and any duplicates are removed. |
485 | | - * This avoids generating redundant index entries. |
486 | | - */ |
487 | 482 | Datum * |
488 | 483 | ginExtractEntries(GinState *ginstate, OffsetNumber attnum, |
489 | 484 | Datum value, bool isNull, |
@@ -729,3 +724,88 @@ ginbuildphasename(int64 phasenum) |
729 | 724 | return NULL; |
730 | 725 | } |
731 | 726 | } |
| 727 | + |
| 728 | +/* |
| 729 | + * gincomparedatums - Compare two datums to determine if they produce identical keys |
| 730 | + * |
| 731 | + * This function extracts keys from both old_datum and new_datum using the |
| 732 | + * opclass's extractValue function, then compares the extracted key arrays. |
| 733 | + * Returns true if the key sets are identical (same keys, same counts). |
| 734 | + * |
| 735 | + * This enables HOT updates for GIN indexes when the indexed portions of a |
| 736 | + * value haven't changed, even if the value itself has changed. |
| 737 | + * |
| 738 | + * Example: JSONB column with GIN index. If an update changes a non-indexed |
| 739 | + * key in the JSONB document, the extracted keys are identical and we can |
| 740 | + * do a HOT update. |
| 741 | + */ |
| 742 | +bool |
| 743 | +gincomparedatums(Relation index, int attnum, |
| 744 | + Datum old_datum, bool old_isnull, |
| 745 | + Datum new_datum, bool new_isnull) |
| 746 | +{ |
| 747 | + GinState ginstate; |
| 748 | + Datum *old_keys; |
| 749 | + Datum *new_keys; |
| 750 | + GinNullCategory *old_categories; |
| 751 | + GinNullCategory *new_categories; |
| 752 | + int32 old_nkeys; |
| 753 | + int32 new_nkeys; |
| 754 | + MemoryContext tmpcontext; |
| 755 | + MemoryContext oldcontext; |
| 756 | + bool result = true; |
| 757 | + |
| 758 | + /* Handle NULL cases */ |
| 759 | + if (old_isnull != new_isnull) |
| 760 | + return false; |
| 761 | + if (old_isnull) |
| 762 | + return true; |
| 763 | + |
| 764 | + /* Create temporary context for extraction work */ |
| 765 | + tmpcontext = AllocSetContextCreate(CurrentMemoryContext, |
| 766 | + "GIN datum comparison", |
| 767 | + ALLOCSET_DEFAULT_SIZES); |
| 768 | + oldcontext = MemoryContextSwitchTo(tmpcontext); |
| 769 | + |
| 770 | + initGinState(&ginstate, index); |
| 771 | + |
| 772 | + /* |
| 773 | + * Extract keys from both datums using existing GIN infrastructure. |
| 774 | + */ |
| 775 | + old_keys = ginExtractEntries(&ginstate, attnum, old_datum, old_isnull, |
| 776 | + &old_nkeys, &old_categories); |
| 777 | + new_keys = ginExtractEntries(&ginstate, attnum, new_datum, new_isnull, |
| 778 | + &new_nkeys, &new_categories); |
| 779 | + |
| 780 | + /* Different number of keys → definitely different */ |
| 781 | + if (old_nkeys != new_nkeys) |
| 782 | + { |
| 783 | + result = false; |
| 784 | + goto cleanup; |
| 785 | + } |
| 786 | + |
| 787 | + /* |
| 788 | + * Compare the sorted key arrays element-by-element. Since both arrays are |
| 789 | + * already sorted by ginExtractEntries, we can do a simple O(n) |
| 790 | + * comparison. |
| 791 | + */ |
| 792 | + for (int i = 0; i < old_nkeys; i++) |
| 793 | + { |
| 794 | + int cmp = ginCompareEntries(&ginstate, attnum, |
| 795 | + old_keys[i], old_categories[i], |
| 796 | + new_keys[i], new_categories[i]); |
| 797 | + |
| 798 | + if (cmp != 0) |
| 799 | + { |
| 800 | + result = false; |
| 801 | + break; |
| 802 | + } |
| 803 | + } |
| 804 | + |
| 805 | +cleanup: |
| 806 | + /* Clean up */ |
| 807 | + MemoryContextSwitchTo(oldcontext); |
| 808 | + MemoryContextDelete(tmpcontext); |
| 809 | + |
| 810 | + return result; |
| 811 | +} |
0 commit comments