/src/postgres/src/backend/utils/cache/ts_cache.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * ts_cache.c |
4 | | * Tsearch related object caches. |
5 | | * |
6 | | * Tsearch performance is very sensitive to performance of parsers, |
7 | | * dictionaries and mapping, so lookups should be cached as much |
8 | | * as possible. |
9 | | * |
10 | | * Once a backend has created a cache entry for a particular TS object OID, |
11 | | * the cache entry will exist for the life of the backend; hence it is |
12 | | * safe to hold onto a pointer to the cache entry while doing things that |
13 | | * might result in recognizing a cache invalidation. Beware however that |
14 | | * subsidiary information might be deleted and reallocated somewhere else |
15 | | * if a cache inval and reval happens! This does not look like it will be |
16 | | * a big problem as long as parser and dictionary methods do not attempt |
17 | | * any database access. |
18 | | * |
19 | | * |
20 | | * Copyright (c) 2006-2026, PostgreSQL Global Development Group |
21 | | * |
22 | | * IDENTIFICATION |
23 | | * src/backend/utils/cache/ts_cache.c |
24 | | * |
25 | | *------------------------------------------------------------------------- |
26 | | */ |
27 | | #include "postgres.h" |
28 | | |
29 | | #include "access/genam.h" |
30 | | #include "access/htup_details.h" |
31 | | #include "access/table.h" |
32 | | #include "access/xact.h" |
33 | | #include "catalog/namespace.h" |
34 | | #include "catalog/pg_ts_config.h" |
35 | | #include "catalog/pg_ts_config_map.h" |
36 | | #include "catalog/pg_ts_dict.h" |
37 | | #include "catalog/pg_ts_parser.h" |
38 | | #include "catalog/pg_ts_template.h" |
39 | | #include "commands/defrem.h" |
40 | | #include "miscadmin.h" |
41 | | #include "nodes/miscnodes.h" |
42 | | #include "tsearch/ts_cache.h" |
43 | | #include "utils/builtins.h" |
44 | | #include "utils/catcache.h" |
45 | | #include "utils/fmgroids.h" |
46 | | #include "utils/guc_hooks.h" |
47 | | #include "utils/hsearch.h" |
48 | | #include "utils/inval.h" |
49 | | #include "utils/lsyscache.h" |
50 | | #include "utils/memutils.h" |
51 | | #include "utils/regproc.h" |
52 | | #include "utils/syscache.h" |
53 | | |
54 | | |
55 | | /* |
56 | | * MAXTOKENTYPE/MAXDICTSPERTT are arbitrary limits on the workspace size |
57 | | * used in lookup_ts_config_cache(). We could avoid hardwiring a limit |
58 | | * by making the workspace dynamically enlargeable, but it seems unlikely |
59 | | * to be worth the trouble. |
60 | | */ |
61 | 0 | #define MAXTOKENTYPE 256 |
62 | 0 | #define MAXDICTSPERTT 100 |
63 | | |
64 | | |
65 | | static HTAB *TSParserCacheHash = NULL; |
66 | | static TSParserCacheEntry *lastUsedParser = NULL; |
67 | | |
68 | | static HTAB *TSDictionaryCacheHash = NULL; |
69 | | static TSDictionaryCacheEntry *lastUsedDictionary = NULL; |
70 | | |
71 | | static HTAB *TSConfigCacheHash = NULL; |
72 | | static TSConfigCacheEntry *lastUsedConfig = NULL; |
73 | | |
74 | | /* |
75 | | * GUC default_text_search_config, and a cache of the current config's OID |
76 | | */ |
77 | | char *TSCurrentConfig = NULL; |
78 | | |
79 | | static Oid TSCurrentConfigCache = InvalidOid; |
80 | | |
81 | | |
82 | | /* |
83 | | * We use this syscache callback to detect when a visible change to a TS |
84 | | * catalog entry has been made, by either our own backend or another one. |
85 | | * |
86 | | * In principle we could just flush the specific cache entry that changed, |
87 | | * but given that TS configuration changes are probably infrequent, it |
88 | | * doesn't seem worth the trouble to determine that; we just flush all the |
89 | | * entries of the related hash table. |
90 | | * |
91 | | * We can use the same function for all TS caches by passing the hash |
92 | | * table address as the "arg". |
93 | | */ |
94 | | static void |
95 | | InvalidateTSCacheCallBack(Datum arg, SysCacheIdentifier cacheid, uint32 hashvalue) |
96 | 0 | { |
97 | 0 | HTAB *hash = (HTAB *) DatumGetPointer(arg); |
98 | 0 | HASH_SEQ_STATUS status; |
99 | 0 | TSAnyCacheEntry *entry; |
100 | |
|
101 | 0 | hash_seq_init(&status, hash); |
102 | 0 | while ((entry = (TSAnyCacheEntry *) hash_seq_search(&status)) != NULL) |
103 | 0 | entry->isvalid = false; |
104 | | |
105 | | /* Also invalidate the current-config cache if it's pg_ts_config */ |
106 | 0 | if (hash == TSConfigCacheHash) |
107 | 0 | TSCurrentConfigCache = InvalidOid; |
108 | 0 | } |
109 | | |
110 | | /* |
111 | | * Fetch parser cache entry |
112 | | */ |
113 | | TSParserCacheEntry * |
114 | | lookup_ts_parser_cache(Oid prsId) |
115 | 0 | { |
116 | 0 | TSParserCacheEntry *entry; |
117 | |
|
118 | 0 | if (TSParserCacheHash == NULL) |
119 | 0 | { |
120 | | /* First time through: initialize the hash table */ |
121 | 0 | HASHCTL ctl; |
122 | |
|
123 | 0 | ctl.keysize = sizeof(Oid); |
124 | 0 | ctl.entrysize = sizeof(TSParserCacheEntry); |
125 | 0 | TSParserCacheHash = hash_create("Tsearch parser cache", 4, |
126 | 0 | &ctl, HASH_ELEM | HASH_BLOBS); |
127 | | /* Flush cache on pg_ts_parser changes */ |
128 | 0 | CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack, |
129 | 0 | PointerGetDatum(TSParserCacheHash)); |
130 | | |
131 | | /* Also make sure CacheMemoryContext exists */ |
132 | 0 | if (!CacheMemoryContext) |
133 | 0 | CreateCacheMemoryContext(); |
134 | 0 | } |
135 | | |
136 | | /* Check single-entry cache */ |
137 | 0 | if (lastUsedParser && lastUsedParser->prsId == prsId && |
138 | 0 | lastUsedParser->isvalid) |
139 | 0 | return lastUsedParser; |
140 | | |
141 | | /* Try to look up an existing entry */ |
142 | 0 | entry = (TSParserCacheEntry *) hash_search(TSParserCacheHash, |
143 | 0 | &prsId, |
144 | 0 | HASH_FIND, NULL); |
145 | 0 | if (entry == NULL || !entry->isvalid) |
146 | 0 | { |
147 | | /* |
148 | | * If we didn't find one, we want to make one. But first look up the |
149 | | * object to be sure the OID is real. |
150 | | */ |
151 | 0 | HeapTuple tp; |
152 | 0 | Form_pg_ts_parser prs; |
153 | |
|
154 | 0 | tp = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId)); |
155 | 0 | if (!HeapTupleIsValid(tp)) |
156 | 0 | elog(ERROR, "cache lookup failed for text search parser %u", |
157 | 0 | prsId); |
158 | 0 | prs = (Form_pg_ts_parser) GETSTRUCT(tp); |
159 | | |
160 | | /* |
161 | | * Sanity checks |
162 | | */ |
163 | 0 | if (!OidIsValid(prs->prsstart)) |
164 | 0 | elog(ERROR, "text search parser %u has no prsstart method", prsId); |
165 | 0 | if (!OidIsValid(prs->prstoken)) |
166 | 0 | elog(ERROR, "text search parser %u has no prstoken method", prsId); |
167 | 0 | if (!OidIsValid(prs->prsend)) |
168 | 0 | elog(ERROR, "text search parser %u has no prsend method", prsId); |
169 | | |
170 | 0 | if (entry == NULL) |
171 | 0 | { |
172 | 0 | bool found; |
173 | | |
174 | | /* Now make the cache entry */ |
175 | 0 | entry = (TSParserCacheEntry *) |
176 | 0 | hash_search(TSParserCacheHash, &prsId, HASH_ENTER, &found); |
177 | 0 | Assert(!found); /* it wasn't there a moment ago */ |
178 | 0 | } |
179 | |
|
180 | 0 | MemSet(entry, 0, sizeof(TSParserCacheEntry)); |
181 | 0 | entry->prsId = prsId; |
182 | 0 | entry->startOid = prs->prsstart; |
183 | 0 | entry->tokenOid = prs->prstoken; |
184 | 0 | entry->endOid = prs->prsend; |
185 | 0 | entry->headlineOid = prs->prsheadline; |
186 | 0 | entry->lextypeOid = prs->prslextype; |
187 | |
|
188 | 0 | ReleaseSysCache(tp); |
189 | |
|
190 | 0 | fmgr_info_cxt(entry->startOid, &entry->prsstart, CacheMemoryContext); |
191 | 0 | fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext); |
192 | 0 | fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext); |
193 | 0 | if (OidIsValid(entry->headlineOid)) |
194 | 0 | fmgr_info_cxt(entry->headlineOid, &entry->prsheadline, |
195 | 0 | CacheMemoryContext); |
196 | |
|
197 | 0 | entry->isvalid = true; |
198 | 0 | } |
199 | | |
200 | 0 | lastUsedParser = entry; |
201 | |
|
202 | 0 | return entry; |
203 | 0 | } |
204 | | |
205 | | /* |
206 | | * Fetch dictionary cache entry |
207 | | */ |
208 | | TSDictionaryCacheEntry * |
209 | | lookup_ts_dictionary_cache(Oid dictId) |
210 | 0 | { |
211 | 0 | TSDictionaryCacheEntry *entry; |
212 | |
|
213 | 0 | if (TSDictionaryCacheHash == NULL) |
214 | 0 | { |
215 | | /* First time through: initialize the hash table */ |
216 | 0 | HASHCTL ctl; |
217 | |
|
218 | 0 | ctl.keysize = sizeof(Oid); |
219 | 0 | ctl.entrysize = sizeof(TSDictionaryCacheEntry); |
220 | 0 | TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8, |
221 | 0 | &ctl, HASH_ELEM | HASH_BLOBS); |
222 | | /* Flush cache on pg_ts_dict and pg_ts_template changes */ |
223 | 0 | CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack, |
224 | 0 | PointerGetDatum(TSDictionaryCacheHash)); |
225 | 0 | CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack, |
226 | 0 | PointerGetDatum(TSDictionaryCacheHash)); |
227 | | |
228 | | /* Also make sure CacheMemoryContext exists */ |
229 | 0 | if (!CacheMemoryContext) |
230 | 0 | CreateCacheMemoryContext(); |
231 | 0 | } |
232 | | |
233 | | /* Check single-entry cache */ |
234 | 0 | if (lastUsedDictionary && lastUsedDictionary->dictId == dictId && |
235 | 0 | lastUsedDictionary->isvalid) |
236 | 0 | return lastUsedDictionary; |
237 | | |
238 | | /* Try to look up an existing entry */ |
239 | 0 | entry = (TSDictionaryCacheEntry *) hash_search(TSDictionaryCacheHash, |
240 | 0 | &dictId, |
241 | 0 | HASH_FIND, NULL); |
242 | 0 | if (entry == NULL || !entry->isvalid) |
243 | 0 | { |
244 | | /* |
245 | | * If we didn't find one, we want to make one. But first look up the |
246 | | * object to be sure the OID is real. |
247 | | */ |
248 | 0 | HeapTuple tpdict, |
249 | 0 | tptmpl; |
250 | 0 | Form_pg_ts_dict dict; |
251 | 0 | Form_pg_ts_template template; |
252 | 0 | MemoryContext saveCtx; |
253 | |
|
254 | 0 | tpdict = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId)); |
255 | 0 | if (!HeapTupleIsValid(tpdict)) |
256 | 0 | elog(ERROR, "cache lookup failed for text search dictionary %u", |
257 | 0 | dictId); |
258 | 0 | dict = (Form_pg_ts_dict) GETSTRUCT(tpdict); |
259 | | |
260 | | /* |
261 | | * Sanity checks |
262 | | */ |
263 | 0 | if (!OidIsValid(dict->dicttemplate)) |
264 | 0 | elog(ERROR, "text search dictionary %u has no template", dictId); |
265 | | |
266 | | /* |
267 | | * Retrieve dictionary's template |
268 | | */ |
269 | 0 | tptmpl = SearchSysCache1(TSTEMPLATEOID, |
270 | 0 | ObjectIdGetDatum(dict->dicttemplate)); |
271 | 0 | if (!HeapTupleIsValid(tptmpl)) |
272 | 0 | elog(ERROR, "cache lookup failed for text search template %u", |
273 | 0 | dict->dicttemplate); |
274 | 0 | template = (Form_pg_ts_template) GETSTRUCT(tptmpl); |
275 | | |
276 | | /* |
277 | | * Sanity checks |
278 | | */ |
279 | 0 | if (!OidIsValid(template->tmpllexize)) |
280 | 0 | elog(ERROR, "text search template %u has no lexize method", |
281 | 0 | template->tmpllexize); |
282 | | |
283 | | /* |
284 | | * OK, create or clear out the hashtable entry |
285 | | */ |
286 | 0 | if (entry == NULL) |
287 | 0 | { |
288 | 0 | bool found; |
289 | |
|
290 | 0 | entry = (TSDictionaryCacheEntry *) |
291 | 0 | hash_search(TSDictionaryCacheHash, |
292 | 0 | &dictId, |
293 | 0 | HASH_ENTER, &found); |
294 | 0 | Assert(!found); /* it wasn't there a moment ago */ |
295 | |
|
296 | 0 | memset(entry, 0, sizeof(TSDictionaryCacheEntry)); |
297 | 0 | entry->dictId = dictId; |
298 | 0 | saveCtx = NULL; |
299 | 0 | } |
300 | 0 | else |
301 | 0 | { |
302 | 0 | saveCtx = entry->dictCtx; /* could be NULL if we failed before */ |
303 | 0 | memset(entry, 0, sizeof(TSDictionaryCacheEntry)); |
304 | 0 | entry->dictId = dictId; |
305 | 0 | entry->dictCtx = saveCtx; |
306 | 0 | } |
307 | | |
308 | | /* |
309 | | * Create or clear the entry's private memory context |
310 | | */ |
311 | 0 | if (saveCtx == NULL) |
312 | 0 | { |
313 | 0 | saveCtx = AllocSetContextCreate(CacheMemoryContext, |
314 | 0 | "TS dictionary", |
315 | 0 | ALLOCSET_SMALL_SIZES); |
316 | 0 | entry->dictCtx = saveCtx; |
317 | 0 | MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname)); |
318 | 0 | } |
319 | 0 | else |
320 | 0 | { |
321 | | /* Don't let context's ident pointer dangle while we reset it */ |
322 | 0 | MemoryContextSetIdentifier(saveCtx, NULL); |
323 | 0 | MemoryContextReset(saveCtx); |
324 | 0 | MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname)); |
325 | 0 | } |
326 | |
|
327 | 0 | entry->lexizeOid = template->tmpllexize; |
328 | |
|
329 | 0 | if (OidIsValid(template->tmplinit)) |
330 | 0 | { |
331 | 0 | List *dictoptions; |
332 | 0 | Datum opt; |
333 | 0 | bool isnull; |
334 | 0 | MemoryContext oldcontext; |
335 | | |
336 | | /* |
337 | | * Init method runs in dictionary's private memory context, and we |
338 | | * make sure the options are stored there too. This typically |
339 | | * results in a small amount of memory leakage, but it's not worth |
340 | | * complicating the API for tmplinit functions to avoid it. |
341 | | */ |
342 | 0 | oldcontext = MemoryContextSwitchTo(entry->dictCtx); |
343 | |
|
344 | 0 | opt = SysCacheGetAttr(TSDICTOID, tpdict, |
345 | 0 | Anum_pg_ts_dict_dictinitoption, |
346 | 0 | &isnull); |
347 | 0 | if (isnull) |
348 | 0 | dictoptions = NIL; |
349 | 0 | else |
350 | 0 | dictoptions = deserialize_deflist(opt); |
351 | |
|
352 | 0 | entry->dictData = |
353 | 0 | DatumGetPointer(OidFunctionCall1(template->tmplinit, |
354 | 0 | PointerGetDatum(dictoptions))); |
355 | |
|
356 | 0 | MemoryContextSwitchTo(oldcontext); |
357 | 0 | } |
358 | |
|
359 | 0 | ReleaseSysCache(tptmpl); |
360 | 0 | ReleaseSysCache(tpdict); |
361 | |
|
362 | 0 | fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx); |
363 | |
|
364 | 0 | entry->isvalid = true; |
365 | 0 | } |
366 | | |
367 | 0 | lastUsedDictionary = entry; |
368 | |
|
369 | 0 | return entry; |
370 | 0 | } |
371 | | |
372 | | /* |
373 | | * Initialize config cache and prepare callbacks. This is split out of |
374 | | * lookup_ts_config_cache because we need to activate the callback before |
375 | | * caching TSCurrentConfigCache, too. |
376 | | */ |
377 | | static void |
378 | | init_ts_config_cache(void) |
379 | 0 | { |
380 | 0 | HASHCTL ctl; |
381 | |
|
382 | 0 | ctl.keysize = sizeof(Oid); |
383 | 0 | ctl.entrysize = sizeof(TSConfigCacheEntry); |
384 | 0 | TSConfigCacheHash = hash_create("Tsearch configuration cache", 16, |
385 | 0 | &ctl, HASH_ELEM | HASH_BLOBS); |
386 | | /* Flush cache on pg_ts_config and pg_ts_config_map changes */ |
387 | 0 | CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack, |
388 | 0 | PointerGetDatum(TSConfigCacheHash)); |
389 | 0 | CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack, |
390 | 0 | PointerGetDatum(TSConfigCacheHash)); |
391 | | |
392 | | /* Also make sure CacheMemoryContext exists */ |
393 | 0 | if (!CacheMemoryContext) |
394 | 0 | CreateCacheMemoryContext(); |
395 | 0 | } |
396 | | |
397 | | /* |
398 | | * Fetch configuration cache entry |
399 | | */ |
400 | | TSConfigCacheEntry * |
401 | | lookup_ts_config_cache(Oid cfgId) |
402 | 0 | { |
403 | 0 | TSConfigCacheEntry *entry; |
404 | |
|
405 | 0 | if (TSConfigCacheHash == NULL) |
406 | 0 | { |
407 | | /* First time through: initialize the hash table */ |
408 | 0 | init_ts_config_cache(); |
409 | 0 | } |
410 | | |
411 | | /* Check single-entry cache */ |
412 | 0 | if (lastUsedConfig && lastUsedConfig->cfgId == cfgId && |
413 | 0 | lastUsedConfig->isvalid) |
414 | 0 | return lastUsedConfig; |
415 | | |
416 | | /* Try to look up an existing entry */ |
417 | 0 | entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash, |
418 | 0 | &cfgId, |
419 | 0 | HASH_FIND, NULL); |
420 | 0 | if (entry == NULL || !entry->isvalid) |
421 | 0 | { |
422 | | /* |
423 | | * If we didn't find one, we want to make one. But first look up the |
424 | | * object to be sure the OID is real. |
425 | | */ |
426 | 0 | HeapTuple tp; |
427 | 0 | Form_pg_ts_config cfg; |
428 | 0 | Relation maprel; |
429 | 0 | Relation mapidx; |
430 | 0 | ScanKeyData mapskey; |
431 | 0 | SysScanDesc mapscan; |
432 | 0 | HeapTuple maptup; |
433 | 0 | ListDictionary maplists[MAXTOKENTYPE + 1]; |
434 | 0 | Oid mapdicts[MAXDICTSPERTT]; |
435 | 0 | int maxtokentype; |
436 | 0 | int ndicts; |
437 | 0 | int i; |
438 | |
|
439 | 0 | tp = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId)); |
440 | 0 | if (!HeapTupleIsValid(tp)) |
441 | 0 | elog(ERROR, "cache lookup failed for text search configuration %u", |
442 | 0 | cfgId); |
443 | 0 | cfg = (Form_pg_ts_config) GETSTRUCT(tp); |
444 | | |
445 | | /* |
446 | | * Sanity checks |
447 | | */ |
448 | 0 | if (!OidIsValid(cfg->cfgparser)) |
449 | 0 | elog(ERROR, "text search configuration %u has no parser", cfgId); |
450 | | |
451 | 0 | if (entry == NULL) |
452 | 0 | { |
453 | 0 | bool found; |
454 | | |
455 | | /* Now make the cache entry */ |
456 | 0 | entry = (TSConfigCacheEntry *) |
457 | 0 | hash_search(TSConfigCacheHash, |
458 | 0 | &cfgId, |
459 | 0 | HASH_ENTER, &found); |
460 | 0 | Assert(!found); /* it wasn't there a moment ago */ |
461 | 0 | } |
462 | 0 | else |
463 | 0 | { |
464 | | /* Cleanup old contents */ |
465 | 0 | if (entry->map) |
466 | 0 | { |
467 | 0 | for (i = 0; i < entry->lenmap; i++) |
468 | 0 | if (entry->map[i].dictIds) |
469 | 0 | pfree(entry->map[i].dictIds); |
470 | 0 | pfree(entry->map); |
471 | 0 | } |
472 | 0 | } |
473 | |
|
474 | 0 | MemSet(entry, 0, sizeof(TSConfigCacheEntry)); |
475 | 0 | entry->cfgId = cfgId; |
476 | 0 | entry->prsId = cfg->cfgparser; |
477 | |
|
478 | 0 | ReleaseSysCache(tp); |
479 | | |
480 | | /* |
481 | | * Scan pg_ts_config_map to gather dictionary list for each token type |
482 | | * |
483 | | * Because the index is on (mapcfg, maptokentype, mapseqno), we will |
484 | | * see the entries in maptokentype order, and in mapseqno order for |
485 | | * each token type, even though we didn't explicitly ask for that. |
486 | | */ |
487 | 0 | MemSet(maplists, 0, sizeof(maplists)); |
488 | 0 | maxtokentype = 0; |
489 | 0 | ndicts = 0; |
490 | |
|
491 | 0 | ScanKeyInit(&mapskey, |
492 | 0 | Anum_pg_ts_config_map_mapcfg, |
493 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
494 | 0 | ObjectIdGetDatum(cfgId)); |
495 | |
|
496 | 0 | maprel = table_open(TSConfigMapRelationId, AccessShareLock); |
497 | 0 | mapidx = index_open(TSConfigMapIndexId, AccessShareLock); |
498 | 0 | mapscan = systable_beginscan_ordered(maprel, mapidx, |
499 | 0 | NULL, 1, &mapskey); |
500 | |
|
501 | 0 | while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL) |
502 | 0 | { |
503 | 0 | Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); |
504 | 0 | int toktype = cfgmap->maptokentype; |
505 | |
|
506 | 0 | if (toktype <= 0 || toktype > MAXTOKENTYPE) |
507 | 0 | elog(ERROR, "maptokentype value %d is out of range", toktype); |
508 | 0 | if (toktype < maxtokentype) |
509 | 0 | elog(ERROR, "maptokentype entries are out of order"); |
510 | 0 | if (toktype > maxtokentype) |
511 | 0 | { |
512 | | /* starting a new token type, but first save the prior data */ |
513 | 0 | if (ndicts > 0) |
514 | 0 | { |
515 | 0 | maplists[maxtokentype].len = ndicts; |
516 | 0 | maplists[maxtokentype].dictIds = (Oid *) |
517 | 0 | MemoryContextAlloc(CacheMemoryContext, |
518 | 0 | sizeof(Oid) * ndicts); |
519 | 0 | memcpy(maplists[maxtokentype].dictIds, mapdicts, |
520 | 0 | sizeof(Oid) * ndicts); |
521 | 0 | } |
522 | 0 | maxtokentype = toktype; |
523 | 0 | mapdicts[0] = cfgmap->mapdict; |
524 | 0 | ndicts = 1; |
525 | 0 | } |
526 | 0 | else |
527 | 0 | { |
528 | | /* continuing data for current token type */ |
529 | 0 | if (ndicts >= MAXDICTSPERTT) |
530 | 0 | elog(ERROR, "too many pg_ts_config_map entries for one token type"); |
531 | 0 | mapdicts[ndicts++] = cfgmap->mapdict; |
532 | 0 | } |
533 | 0 | } |
534 | | |
535 | 0 | systable_endscan_ordered(mapscan); |
536 | 0 | index_close(mapidx, AccessShareLock); |
537 | 0 | table_close(maprel, AccessShareLock); |
538 | |
|
539 | 0 | if (ndicts > 0) |
540 | 0 | { |
541 | | /* save the last token type's dictionaries */ |
542 | 0 | maplists[maxtokentype].len = ndicts; |
543 | 0 | maplists[maxtokentype].dictIds = (Oid *) |
544 | 0 | MemoryContextAlloc(CacheMemoryContext, |
545 | 0 | sizeof(Oid) * ndicts); |
546 | 0 | memcpy(maplists[maxtokentype].dictIds, mapdicts, |
547 | 0 | sizeof(Oid) * ndicts); |
548 | | /* and save the overall map */ |
549 | 0 | entry->lenmap = maxtokentype + 1; |
550 | 0 | entry->map = (ListDictionary *) |
551 | 0 | MemoryContextAlloc(CacheMemoryContext, |
552 | 0 | sizeof(ListDictionary) * entry->lenmap); |
553 | 0 | memcpy(entry->map, maplists, |
554 | 0 | sizeof(ListDictionary) * entry->lenmap); |
555 | 0 | } |
556 | |
|
557 | 0 | entry->isvalid = true; |
558 | 0 | } |
559 | | |
560 | 0 | lastUsedConfig = entry; |
561 | |
|
562 | 0 | return entry; |
563 | 0 | } |
564 | | |
565 | | |
566 | | /*--------------------------------------------------- |
567 | | * GUC variable "default_text_search_config" |
568 | | *--------------------------------------------------- |
569 | | */ |
570 | | |
571 | | Oid |
572 | | getTSCurrentConfig(bool emitError) |
573 | 0 | { |
574 | 0 | List *namelist; |
575 | | |
576 | | /* if we have a cached value, return it */ |
577 | 0 | if (OidIsValid(TSCurrentConfigCache)) |
578 | 0 | return TSCurrentConfigCache; |
579 | | |
580 | | /* fail if GUC hasn't been set up yet */ |
581 | 0 | if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0') |
582 | 0 | { |
583 | 0 | if (emitError) |
584 | 0 | elog(ERROR, "text search configuration isn't set"); |
585 | 0 | else |
586 | 0 | return InvalidOid; |
587 | 0 | } |
588 | | |
589 | 0 | if (TSConfigCacheHash == NULL) |
590 | 0 | { |
591 | | /* First time through: initialize the tsconfig inval callback */ |
592 | 0 | init_ts_config_cache(); |
593 | 0 | } |
594 | | |
595 | | /* Look up the config */ |
596 | 0 | if (emitError) |
597 | 0 | { |
598 | 0 | namelist = stringToQualifiedNameList(TSCurrentConfig, NULL); |
599 | 0 | TSCurrentConfigCache = get_ts_config_oid(namelist, false); |
600 | 0 | } |
601 | 0 | else |
602 | 0 | { |
603 | 0 | ErrorSaveContext escontext = {T_ErrorSaveContext}; |
604 | |
|
605 | 0 | namelist = stringToQualifiedNameList(TSCurrentConfig, |
606 | 0 | (Node *) &escontext); |
607 | 0 | if (namelist != NIL) |
608 | 0 | TSCurrentConfigCache = get_ts_config_oid(namelist, true); |
609 | 0 | else |
610 | 0 | TSCurrentConfigCache = InvalidOid; /* bad name list syntax */ |
611 | 0 | } |
612 | |
|
613 | 0 | return TSCurrentConfigCache; |
614 | 0 | } |
615 | | |
616 | | /* GUC check_hook for default_text_search_config */ |
617 | | bool |
618 | | check_default_text_search_config(char **newval, void **extra, GucSource source) |
619 | 2 | { |
620 | | /* |
621 | | * If we aren't inside a transaction, or connected to a database, we |
622 | | * cannot do the catalog accesses necessary to verify the config name. |
623 | | * Must accept it on faith. |
624 | | */ |
625 | 2 | if (IsTransactionState() && MyDatabaseId != InvalidOid) |
626 | 0 | { |
627 | 0 | ErrorSaveContext escontext = {T_ErrorSaveContext}; |
628 | 0 | List *namelist; |
629 | 0 | Oid cfgId; |
630 | 0 | HeapTuple tuple; |
631 | 0 | Form_pg_ts_config cfg; |
632 | 0 | char *buf; |
633 | |
|
634 | 0 | namelist = stringToQualifiedNameList(*newval, |
635 | 0 | (Node *) &escontext); |
636 | 0 | if (namelist != NIL) |
637 | 0 | cfgId = get_ts_config_oid(namelist, true); |
638 | 0 | else |
639 | 0 | cfgId = InvalidOid; /* bad name list syntax */ |
640 | | |
641 | | /* |
642 | | * When source == PGC_S_TEST, don't throw a hard error for a |
643 | | * nonexistent configuration, only a NOTICE. See comments in guc.h. |
644 | | */ |
645 | 0 | if (!OidIsValid(cfgId)) |
646 | 0 | { |
647 | 0 | if (source == PGC_S_TEST) |
648 | 0 | { |
649 | 0 | ereport(NOTICE, |
650 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
651 | 0 | errmsg("text search configuration \"%s\" does not exist", *newval))); |
652 | 0 | return true; |
653 | 0 | } |
654 | 0 | else |
655 | 0 | return false; |
656 | 0 | } |
657 | | |
658 | | /* |
659 | | * Modify the actually stored value to be fully qualified, to ensure |
660 | | * later changes of search_path don't affect it. |
661 | | */ |
662 | 0 | tuple = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId)); |
663 | 0 | if (!HeapTupleIsValid(tuple)) |
664 | 0 | elog(ERROR, "cache lookup failed for text search configuration %u", |
665 | 0 | cfgId); |
666 | 0 | cfg = (Form_pg_ts_config) GETSTRUCT(tuple); |
667 | |
|
668 | 0 | buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace), |
669 | 0 | NameStr(cfg->cfgname)); |
670 | |
|
671 | 0 | ReleaseSysCache(tuple); |
672 | | |
673 | | /* GUC wants it guc_malloc'd not palloc'd */ |
674 | 0 | guc_free(*newval); |
675 | 0 | *newval = guc_strdup(LOG, buf); |
676 | 0 | pfree(buf); |
677 | 0 | if (!*newval) |
678 | 0 | return false; |
679 | 0 | } |
680 | | |
681 | 2 | return true; |
682 | 2 | } |
683 | | |
684 | | /* GUC assign_hook for default_text_search_config */ |
685 | | void |
686 | | assign_default_text_search_config(const char *newval, void *extra) |
687 | 2 | { |
688 | | /* Just reset the cache to force a lookup on first use */ |
689 | 2 | TSCurrentConfigCache = InvalidOid; |
690 | 2 | } |