|
| 1 | +/* ------------------------------------------------------------------------ |
| 2 | + * |
| 3 | + * relation_tags.c |
| 4 | + * Attach custom (Key, Value) pairs to an arbitrary RangeTblEntry |
| 5 | + * |
| 6 | + * Copyright (c) 2017, Postgres Professional |
| 7 | + * |
| 8 | + * ------------------------------------------------------------------------ |
| 9 | + */ |
| 10 | + |
| 11 | +#include "compat/relation_tags.h" |
| 12 | +#include "planner_tree_modification.h" |
| 13 | + |
| 14 | +#include "nodes/nodes.h" |
| 15 | + |
| 16 | + |
| 17 | +/* |
| 18 | + * This table is used to ensure that partitioned relation |
| 19 | + * cant't be used with both and without ONLY modifiers. |
| 20 | + */ |
| 21 | +static HTAB *per_table_relation_tags = NULL; |
| 22 | +static int per_table_relation_tags_refcount = 0; |
| 23 | + |
| 24 | + |
| 25 | +/* private struct stored by parenthood lists */ |
| 26 | +typedef struct |
| 27 | +{ |
| 28 | + Oid relid; /* key (part #1) */ |
| 29 | + uint32 queryId; /* key (part #2) */ |
| 30 | + List *relation_tags; |
| 31 | +} relation_tags_entry; |
| 32 | + |
| 33 | + |
| 34 | +/* Look through RTE's relation tags */ |
| 35 | +List * |
| 36 | +rte_fetch_tag(const uint32 query_id, |
| 37 | + const RangeTblEntry *rte, |
| 38 | + const char *key) |
| 39 | +{ |
| 40 | + relation_tags_entry *htab_entry, |
| 41 | + htab_key = { rte->relid, query_id, NIL /* unused */ }; |
| 42 | + |
| 43 | + AssertArg(rte); |
| 44 | + AssertArg(key); |
| 45 | + |
| 46 | + /* Skip if table is not initialized */ |
| 47 | + if (per_table_relation_tags) |
| 48 | + { |
| 49 | + /* Search by 'htab_key' */ |
| 50 | + htab_entry = hash_search(per_table_relation_tags, |
| 51 | + &htab_key, HASH_FIND, NULL); |
| 52 | + |
| 53 | + if (htab_entry) |
| 54 | + return relation_tags_search(htab_entry->relation_tags, key); |
| 55 | + } |
| 56 | + |
| 57 | + /* Not found, return stub value */ |
| 58 | + return NIL; |
| 59 | +} |
| 60 | + |
| 61 | +/* Attach new relation tag to RTE. Returns KVP with duplicate key. */ |
| 62 | +List * |
| 63 | +rte_attach_tag(const uint32 query_id, |
| 64 | + RangeTblEntry *rte, |
| 65 | + List *key_value_pair) |
| 66 | +{ |
| 67 | + relation_tags_entry *htab_entry, |
| 68 | + htab_key = { rte->relid, query_id, NIL /* unused */ }; |
| 69 | + bool found; |
| 70 | + MemoryContext old_mcxt; |
| 71 | + |
| 72 | + AssertArg(rte); |
| 73 | + AssertArg(key_value_pair && list_length(key_value_pair) == 2); |
| 74 | + |
| 75 | + /* We prefer to initialize this table lazily */ |
| 76 | + if (!per_table_relation_tags) |
| 77 | + { |
| 78 | + const long start_elems = 50; |
| 79 | + HASHCTL hashctl; |
| 80 | + |
| 81 | + memset(&hashctl, 0, sizeof(HASHCTL)); |
| 82 | + hashctl.entrysize = sizeof(relation_tags_entry); |
| 83 | + hashctl.keysize = offsetof(relation_tags_entry, relation_tags); |
| 84 | + hashctl.hcxt = TAG_MEMORY_CONTEXT; |
| 85 | + |
| 86 | + per_table_relation_tags = hash_create("Custom tags for RangeTblEntry", |
| 87 | + start_elems, &hashctl, |
| 88 | + HASH_ELEM | HASH_BLOBS); |
| 89 | + } |
| 90 | + |
| 91 | + /* Search by 'htab_key' */ |
| 92 | + htab_entry = hash_search(per_table_relation_tags, |
| 93 | + &htab_key, HASH_ENTER, &found); |
| 94 | + |
| 95 | + if (found) |
| 96 | + { |
| 97 | + const char *current_key; |
| 98 | + |
| 99 | + /* Extract key of this KVP */ |
| 100 | + rte_deconstruct_tag(key_value_pair, ¤t_key, NULL); |
| 101 | + |
| 102 | + /* Check if this KVP already exists */ |
| 103 | + return relation_tags_search(htab_entry->relation_tags, current_key); |
| 104 | + } |
| 105 | + |
| 106 | + /* Don't forget to initialize list! */ |
| 107 | + else htab_entry->relation_tags = NIL; |
| 108 | + |
| 109 | + /* Add this KVP */ |
| 110 | + old_mcxt = MemoryContextSwitchTo(TAG_MEMORY_CONTEXT); |
| 111 | + htab_entry->relation_tags = lappend(htab_entry->relation_tags, |
| 112 | + key_value_pair); |
| 113 | + MemoryContextSwitchTo(old_mcxt); |
| 114 | + |
| 115 | + /* Success! */ |
| 116 | + return NIL; |
| 117 | +} |
| 118 | + |
| 119 | + |
| 120 | + |
| 121 | +/* Extract key & value from 'key_value_pair' */ |
| 122 | +void |
| 123 | +rte_deconstruct_tag(const List *key_value_pair, |
| 124 | + const char **key, /* ret value #1 */ |
| 125 | + const Value **value) /* ret value #2 */ |
| 126 | +{ |
| 127 | + const char *r_key; |
| 128 | + const Value *r_value; |
| 129 | + |
| 130 | + AssertArg(key_value_pair && list_length(key_value_pair) == 2); |
| 131 | + |
| 132 | + r_key = (const char *) strVal(linitial(key_value_pair)); |
| 133 | + r_value = (const Value *) lsecond(key_value_pair); |
| 134 | + |
| 135 | + /* Check that 'key' is valid */ |
| 136 | + Assert(IsA(linitial(key_value_pair), String)); |
| 137 | + |
| 138 | + /* Check that 'value' is valid or NULL */ |
| 139 | + Assert(r_value == NULL || |
| 140 | + IsA(r_value, Integer) || |
| 141 | + IsA(r_value, Float) || |
| 142 | + IsA(r_value, String)); |
| 143 | + |
| 144 | + /* Finally return key & value */ |
| 145 | + if (key) *key = r_key; |
| 146 | + if (value) *value = r_value; |
| 147 | +} |
| 148 | + |
| 149 | +/* Search through list of 'relation_tags' */ |
| 150 | +List * |
| 151 | +relation_tags_search(List *relation_tags, const char *key) |
| 152 | +{ |
| 153 | + ListCell *lc; |
| 154 | + |
| 155 | + AssertArg(key); |
| 156 | + |
| 157 | + /* Scan KVP list */ |
| 158 | + foreach (lc, relation_tags) |
| 159 | + { |
| 160 | + List *current_kvp = (List *) lfirst(lc); |
| 161 | + const char *current_key; |
| 162 | + |
| 163 | + /* Extract key of this KVP */ |
| 164 | + rte_deconstruct_tag(current_kvp, ¤t_key, NULL); |
| 165 | + |
| 166 | + /* Check if this is the KVP we're looking for */ |
| 167 | + if (strcmp(key, current_key) == 0) |
| 168 | + return current_kvp; |
| 169 | + } |
| 170 | + |
| 171 | + /* Nothing! */ |
| 172 | + return NIL; |
| 173 | +} |
| 174 | + |
| 175 | + |
| 176 | + |
| 177 | +/* Increate usage counter by 1 */ |
| 178 | +void |
| 179 | +incr_refcount_relation_tags(void) |
| 180 | +{ |
| 181 | + /* Increment reference counter */ |
| 182 | + if (++per_table_relation_tags_refcount <= 0) |
| 183 | + elog(WARNING, "imbalanced %s", |
| 184 | + CppAsString(incr_refcount_relation_tags)); |
| 185 | +} |
| 186 | + |
| 187 | +/* Return current value of usage counter */ |
| 188 | +uint32 |
| 189 | +get_refcount_relation_tags(void) |
| 190 | +{ |
| 191 | + /* incr_refcount_parenthood_statuses() is called by pathman_planner_hook() */ |
| 192 | + return per_table_relation_tags_refcount; |
| 193 | +} |
| 194 | + |
| 195 | +/* Reset all cached statuses if needed (query end) */ |
| 196 | +void |
| 197 | +decr_refcount_relation_tags(void) |
| 198 | +{ |
| 199 | + /* Decrement reference counter */ |
| 200 | + if (--per_table_relation_tags_refcount < 0) |
| 201 | + elog(WARNING, "imbalanced %s", |
| 202 | + CppAsString(decr_refcount_relation_tags)); |
| 203 | + |
| 204 | + /* Free resources if no one is using them */ |
| 205 | + if (per_table_relation_tags_refcount == 0) |
| 206 | + { |
| 207 | + reset_query_id_generator(); |
| 208 | + |
| 209 | + hash_destroy(per_table_relation_tags); |
| 210 | + per_table_relation_tags = NULL; |
| 211 | + } |
| 212 | +} |
0 commit comments