|
61 | 61 | #include "access/tupdesc_details.h"
|
62 | 62 | #include "access/tuptoaster.h"
|
63 | 63 | #include "executor/tuptable.h"
|
| 64 | +#include "utils/datum.h" |
64 | 65 | #include "utils/expandeddatum.h"
|
| 66 | +#include "utils/hashutils.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 != 'p')
|
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 DatumGetUInt32( |
| 97 | + hash_any((const unsigned char *) entry->value, entry->len)); |
| 98 | +} |
| 99 | + |
| 100 | +static int |
| 101 | +missing_match(const void *key1, const void *key2, Size keysize) |
| 102 | +{ |
| 103 | + const missing_cache_key *entry1 = (missing_cache_key *) key1; |
| 104 | + const missing_cache_key *entry2 = (missing_cache_key *) key2; |
| 105 | + |
| 106 | + if (entry1->len != entry2->len) |
| 107 | + return entry1->len > entry2->len ? 1 : -1; |
| 108 | + |
| 109 | + return memcmp(DatumGetPointer(entry1->value), |
| 110 | + DatumGetPointer(entry2->value), |
| 111 | + entry1->len); |
| 112 | +} |
| 113 | + |
| 114 | +static void |
| 115 | +init_missing_cache() |
| 116 | +{ |
| 117 | + HASHCTL hash_ctl; |
| 118 | + |
| 119 | + hash_ctl.keysize = sizeof(missing_cache_key); |
| 120 | + hash_ctl.entrysize = sizeof(missing_cache_key); |
| 121 | + hash_ctl.hcxt = TopMemoryContext; |
| 122 | + hash_ctl.hash = missing_hash; |
| 123 | + hash_ctl.match = missing_match; |
| 124 | + missing_cache = |
| 125 | + hash_create("Missing Values Cache", |
| 126 | + 32, |
| 127 | + &hash_ctl, |
| 128 | + HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE); |
| 129 | +} |
74 | 130 |
|
75 | 131 | /* ----------------------------------------------------------------
|
76 | 132 | * misc support routines
|
@@ -102,8 +158,41 @@ getmissingattr(TupleDesc tupleDesc,
|
102 | 158 |
|
103 | 159 | if (attrmiss->am_present)
|
104 | 160 | {
|
| 161 | + missing_cache_key key; |
| 162 | + missing_cache_key *entry; |
| 163 | + bool found; |
| 164 | + MemoryContext oldctx; |
| 165 | + |
105 | 166 | *isnull = false;
|
106 |
| - return attrmiss->am_value; |
| 167 | + |
| 168 | + /* no need to cache by-value attributes */ |
| 169 | + if (att->attbyval) |
| 170 | + return attrmiss->am_value; |
| 171 | + |
| 172 | + /* set up cache if required */ |
| 173 | + if (missing_cache == NULL) |
| 174 | + init_missing_cache(); |
| 175 | + |
| 176 | + /* check if there's a cache entry */ |
| 177 | + Assert(att->attlen > 0 || att->attlen == -1); |
| 178 | + if (att->attlen > 0) |
| 179 | + key.len = att->attlen; |
| 180 | + else |
| 181 | + key.len = VARSIZE_ANY(attrmiss->am_value); |
| 182 | + key.value = attrmiss->am_value; |
| 183 | + |
| 184 | + entry = hash_search(missing_cache, &key, HASH_ENTER, &found); |
| 185 | + |
| 186 | + if (!found) |
| 187 | + { |
| 188 | + /* cache miss, so we need a non-transient copy of the datum */ |
| 189 | + oldctx = MemoryContextSwitchTo(TopMemoryContext); |
| 190 | + entry->value = |
| 191 | + datumCopy(attrmiss->am_value, false, att->attlen); |
| 192 | + MemoryContextSwitchTo(oldctx); |
| 193 | + } |
| 194 | + |
| 195 | + return entry->value; |
107 | 196 | }
|
108 | 197 | }
|
109 | 198 |
|
|
0 commit comments