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