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