|
60 | 60 | #include "access/heaptoast.h"
|
61 | 61 | #include "access/sysattr.h"
|
62 | 62 | #include "access/tupdesc_details.h"
|
| 63 | +#include "common/hashfn.h" |
63 | 64 | #include "executor/tuptable.h"
|
| 65 | +#include "utils/datum.h" |
64 | 66 | #include "utils/expandeddatum.h"
|
| 67 | +#include "utils/hsearch.h" |
| 68 | +#include "utils/memutils.h" |
65 | 69 |
|
66 | 70 |
|
67 | 71 | /* Does att's datatype allow packing into the 1-byte-header varlena format? */
|
|
71 | 75 | #define VARLENA_ATT_IS_PACKABLE(att) \
|
72 | 76 | ((att)->attstorage != TYPSTORAGE_PLAIN)
|
73 | 77 |
|
| 78 | +/* |
| 79 | + * Setup for cacheing pass-by-ref missing attributes in a way that survives |
| 80 | + * tupleDesc destruction. |
| 81 | + */ |
| 82 | + |
| 83 | +typedef struct |
| 84 | +{ |
| 85 | + int len; |
| 86 | + Datum value; |
| 87 | +} missing_cache_key; |
| 88 | + |
| 89 | +static HTAB *missing_cache = NULL; |
| 90 | + |
| 91 | +static uint32 |
| 92 | +missing_hash(const void *key, Size keysize) |
| 93 | +{ |
| 94 | + const missing_cache_key *entry = (missing_cache_key *) key; |
| 95 | + |
| 96 | + return hash_bytes((const unsigned char *) entry->value, entry->len); |
| 97 | +} |
| 98 | + |
| 99 | +static int |
| 100 | +missing_match(const void *key1, const void *key2, Size keysize) |
| 101 | +{ |
| 102 | + const missing_cache_key *entry1 = (missing_cache_key *) key1; |
| 103 | + const missing_cache_key *entry2 = (missing_cache_key *) key2; |
| 104 | + |
| 105 | + if (entry1->len != entry2->len) |
| 106 | + return entry1->len > entry2->len ? 1 : -1; |
| 107 | + |
| 108 | + return memcmp(DatumGetPointer(entry1->value), |
| 109 | + DatumGetPointer(entry2->value), |
| 110 | + entry1->len); |
| 111 | +} |
| 112 | + |
| 113 | +static void |
| 114 | +init_missing_cache() |
| 115 | +{ |
| 116 | + HASHCTL hash_ctl; |
| 117 | + |
| 118 | + hash_ctl.keysize = sizeof(missing_cache_key); |
| 119 | + hash_ctl.entrysize = sizeof(missing_cache_key); |
| 120 | + hash_ctl.hcxt = TopMemoryContext; |
| 121 | + hash_ctl.hash = missing_hash; |
| 122 | + hash_ctl.match = missing_match; |
| 123 | + missing_cache = |
| 124 | + hash_create("Missing Values Cache", |
| 125 | + 32, |
| 126 | + &hash_ctl, |
| 127 | + HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE); |
| 128 | +} |
74 | 129 |
|
75 | 130 | /* ----------------------------------------------------------------
|
76 | 131 | * misc support routines
|
@@ -102,8 +157,41 @@ getmissingattr(TupleDesc tupleDesc,
|
102 | 157 |
|
103 | 158 | if (attrmiss->am_present)
|
104 | 159 | {
|
| 160 | + missing_cache_key key; |
| 161 | + missing_cache_key *entry; |
| 162 | + bool found; |
| 163 | + MemoryContext oldctx; |
| 164 | + |
105 | 165 | *isnull = false;
|
106 |
| - return attrmiss->am_value; |
| 166 | + |
| 167 | + /* no need to cache by-value attributes */ |
| 168 | + if (att->attbyval) |
| 169 | + return attrmiss->am_value; |
| 170 | + |
| 171 | + /* set up cache if required */ |
| 172 | + if (missing_cache == NULL) |
| 173 | + init_missing_cache(); |
| 174 | + |
| 175 | + /* check if there's a cache entry */ |
| 176 | + Assert(att->attlen > 0 || att->attlen == -1); |
| 177 | + if (att->attlen > 0) |
| 178 | + key.len = att->attlen; |
| 179 | + else |
| 180 | + key.len = VARSIZE_ANY(attrmiss->am_value); |
| 181 | + key.value = attrmiss->am_value; |
| 182 | + |
| 183 | + entry = hash_search(missing_cache, &key, HASH_ENTER, &found); |
| 184 | + |
| 185 | + if (!found) |
| 186 | + { |
| 187 | + /* cache miss, so we need a non-transient copy of the datum */ |
| 188 | + oldctx = MemoryContextSwitchTo(TopMemoryContext); |
| 189 | + entry->value = |
| 190 | + datumCopy(attrmiss->am_value, false, att->attlen); |
| 191 | + MemoryContextSwitchTo(oldctx); |
| 192 | + } |
| 193 | + |
| 194 | + return entry->value; |
107 | 195 | }
|
108 | 196 | }
|
109 | 197 |
|
|
0 commit comments