OSDN Git Service

e26c03327a086064ecbc976661d489fc145910f7
[pg-rex/syncrep.git] / 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-2009, PostgreSQL Global Development Group
21  *
22  * IDENTIFICATION
23  *        $PostgreSQL: pgsql/src/backend/utils/cache/ts_cache.c,v 1.10 2009/12/27 18:55:52 tgl Exp $
24  *
25  *-------------------------------------------------------------------------
26  */
27 #include "postgres.h"
28
29 #include "access/genam.h"
30 #include "access/heapam.h"
31 #include "access/xact.h"
32 #include "catalog/indexing.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 "catalog/pg_type.h"
40 #include "commands/defrem.h"
41 #include "miscadmin.h"
42 #include "tsearch/ts_cache.h"
43 #include "utils/array.h"
44 #include "utils/builtins.h"
45 #include "utils/fmgroids.h"
46 #include "utils/inval.h"
47 #include "utils/lsyscache.h"
48 #include "utils/memutils.h"
49 #include "utils/syscache.h"
50 #include "utils/tqual.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, ItemPointer tuplePtr)
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                 ctl.hash = oid_hash;
125                 TSParserCacheHash = hash_create("Tsearch parser cache", 4,
126                                                                                 &ctl, HASH_ELEM | HASH_FUNCTION);
127                 /* Flush cache on pg_ts_parser changes */
128                 CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
129                                                                           PointerGetDatum(TSParserCacheHash));
130
131                 /* Also make sure CacheMemoryContext exists */
132                 if (!CacheMemoryContext)
133                         CreateCacheMemoryContext();
134         }
135
136         /* Check single-entry cache */
137         if (lastUsedParser && lastUsedParser->prsId == prsId &&
138                 lastUsedParser->isvalid)
139                 return lastUsedParser;
140
141         /* Try to look up an existing entry */
142         entry = (TSParserCacheEntry *) hash_search(TSParserCacheHash,
143                                                                                            (void *) &prsId,
144                                                                                            HASH_FIND, NULL);
145         if (entry == NULL || !entry->isvalid)
146         {
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                 HeapTuple       tp;
152                 Form_pg_ts_parser prs;
153
154                 tp = SearchSysCache(TSPARSEROID,
155                                                         ObjectIdGetDatum(prsId),
156                                                         0, 0, 0);
157                 if (!HeapTupleIsValid(tp))
158                         elog(ERROR, "cache lookup failed for text search parser %u",
159                                  prsId);
160                 prs = (Form_pg_ts_parser) GETSTRUCT(tp);
161
162                 /*
163                  * Sanity checks
164                  */
165                 if (!OidIsValid(prs->prsstart))
166                         elog(ERROR, "text search parser %u has no prsstart method", prsId);
167                 if (!OidIsValid(prs->prstoken))
168                         elog(ERROR, "text search parser %u has no prstoken method", prsId);
169                 if (!OidIsValid(prs->prsend))
170                         elog(ERROR, "text search parser %u has no prsend method", prsId);
171
172                 if (entry == NULL)
173                 {
174                         bool            found;
175
176                         /* Now make the cache entry */
177                         entry = (TSParserCacheEntry *)
178                                 hash_search(TSParserCacheHash,
179                                                         (void *) &prsId,
180                                                         HASH_ENTER, &found);
181                         Assert(!found);         /* it wasn't there a moment ago */
182                 }
183
184                 MemSet(entry, 0, sizeof(TSParserCacheEntry));
185                 entry->prsId = prsId;
186                 entry->startOid = prs->prsstart;
187                 entry->tokenOid = prs->prstoken;
188                 entry->endOid = prs->prsend;
189                 entry->headlineOid = prs->prsheadline;
190                 entry->lextypeOid = prs->prslextype;
191
192                 ReleaseSysCache(tp);
193
194                 fmgr_info_cxt(entry->startOid, &entry->prsstart, CacheMemoryContext);
195                 fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext);
196                 fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext);
197                 if (OidIsValid(entry->headlineOid))
198                         fmgr_info_cxt(entry->headlineOid, &entry->prsheadline,
199                                                   CacheMemoryContext);
200
201                 entry->isvalid = true;
202         }
203
204         lastUsedParser = entry;
205
206         return entry;
207 }
208
209 /*
210  * Fetch dictionary cache entry
211  */
212 TSDictionaryCacheEntry *
213 lookup_ts_dictionary_cache(Oid dictId)
214 {
215         TSDictionaryCacheEntry *entry;
216
217         if (TSDictionaryCacheHash == NULL)
218         {
219                 /* First time through: initialize the hash table */
220                 HASHCTL         ctl;
221
222                 MemSet(&ctl, 0, sizeof(ctl));
223                 ctl.keysize = sizeof(Oid);
224                 ctl.entrysize = sizeof(TSDictionaryCacheEntry);
225                 ctl.hash = oid_hash;
226                 TSDictionaryCacheHash = hash_create("Tsearch dictionary cache", 8,
227                                                                                         &ctl, HASH_ELEM | HASH_FUNCTION);
228                 /* Flush cache on pg_ts_dict and pg_ts_template changes */
229                 CacheRegisterSyscacheCallback(TSDICTOID, InvalidateTSCacheCallBack,
230                                                                           PointerGetDatum(TSDictionaryCacheHash));
231                 CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack,
232                                                                           PointerGetDatum(TSDictionaryCacheHash));
233
234                 /* Also make sure CacheMemoryContext exists */
235                 if (!CacheMemoryContext)
236                         CreateCacheMemoryContext();
237         }
238
239         /* Check single-entry cache */
240         if (lastUsedDictionary && lastUsedDictionary->dictId == dictId &&
241                 lastUsedDictionary->isvalid)
242                 return lastUsedDictionary;
243
244         /* Try to look up an existing entry */
245         entry = (TSDictionaryCacheEntry *) hash_search(TSDictionaryCacheHash,
246                                                                                                    (void *) &dictId,
247                                                                                                    HASH_FIND, NULL);
248         if (entry == NULL || !entry->isvalid)
249         {
250                 /*
251                  * If we didn't find one, we want to make one. But first look up the
252                  * object to be sure the OID is real.
253                  */
254                 HeapTuple       tpdict,
255                                         tptmpl;
256                 Form_pg_ts_dict dict;
257                 Form_pg_ts_template template;
258                 MemoryContext saveCtx;
259
260                 tpdict = SearchSysCache(TSDICTOID,
261                                                                 ObjectIdGetDatum(dictId),
262                                                                 0, 0, 0);
263                 if (!HeapTupleIsValid(tpdict))
264                         elog(ERROR, "cache lookup failed for text search dictionary %u",
265                                  dictId);
266                 dict = (Form_pg_ts_dict) GETSTRUCT(tpdict);
267
268                 /*
269                  * Sanity checks
270                  */
271                 if (!OidIsValid(dict->dicttemplate))
272                         elog(ERROR, "text search dictionary %u has no template", dictId);
273
274                 /*
275                  * Retrieve dictionary's template
276                  */
277                 tptmpl = SearchSysCache(TSTEMPLATEOID,
278                                                                 ObjectIdGetDatum(dict->dicttemplate),
279                                                                 0, 0, 0);
280                 if (!HeapTupleIsValid(tptmpl))
281                         elog(ERROR, "cache lookup failed for text search template %u",
282                                  dict->dicttemplate);
283                 template = (Form_pg_ts_template) GETSTRUCT(tptmpl);
284
285                 /*
286                  * Sanity checks
287                  */
288                 if (!OidIsValid(template->tmpllexize))
289                         elog(ERROR, "text search template %u has no lexize method",
290                                  template->tmpllexize);
291
292                 if (entry == NULL)
293                 {
294                         bool            found;
295
296                         /* Now make the cache entry */
297                         entry = (TSDictionaryCacheEntry *)
298                                 hash_search(TSDictionaryCacheHash,
299                                                         (void *) &dictId,
300                                                         HASH_ENTER, &found);
301                         Assert(!found);         /* it wasn't there a moment ago */
302
303                         /* Create private memory context the first time through */
304                         saveCtx = AllocSetContextCreate(CacheMemoryContext,
305                                                                                         NameStr(dict->dictname),
306                                                                                         ALLOCSET_SMALL_MINSIZE,
307                                                                                         ALLOCSET_SMALL_INITSIZE,
308                                                                                         ALLOCSET_SMALL_MAXSIZE);
309                 }
310                 else
311                 {
312                         /* Clear the existing entry's private context */
313                         saveCtx = entry->dictCtx;
314                         MemoryContextResetAndDeleteChildren(saveCtx);
315                 }
316
317                 MemSet(entry, 0, sizeof(TSDictionaryCacheEntry));
318                 entry->dictId = dictId;
319                 entry->dictCtx = saveCtx;
320
321                 entry->lexizeOid = template->tmpllexize;
322
323                 if (OidIsValid(template->tmplinit))
324                 {
325                         List       *dictoptions;
326                         Datum           opt;
327                         bool            isnull;
328                         MemoryContext oldcontext;
329
330                         /*
331                          * Init method runs in dictionary's private memory context, and we
332                          * make sure the options are stored there too
333                          */
334                         oldcontext = MemoryContextSwitchTo(entry->dictCtx);
335
336                         opt = SysCacheGetAttr(TSDICTOID, tpdict,
337                                                                   Anum_pg_ts_dict_dictinitoption,
338                                                                   &isnull);
339                         if (isnull)
340                                 dictoptions = NIL;
341                         else
342                                 dictoptions = deserialize_deflist(opt);
343
344                         entry->dictData =
345                                 DatumGetPointer(OidFunctionCall1(template->tmplinit,
346                                                                                           PointerGetDatum(dictoptions)));
347
348                         MemoryContextSwitchTo(oldcontext);
349                 }
350
351                 ReleaseSysCache(tptmpl);
352                 ReleaseSysCache(tpdict);
353
354                 fmgr_info_cxt(entry->lexizeOid, &entry->lexize, entry->dictCtx);
355
356                 entry->isvalid = true;
357         }
358
359         lastUsedDictionary = entry;
360
361         return entry;
362 }
363
364 /*
365  * Initialize config cache and prepare callbacks.  This is split out of
366  * lookup_ts_config_cache because we need to activate the callback before
367  * caching TSCurrentConfigCache, too.
368  */
369 static void
370 init_ts_config_cache(void)
371 {
372         HASHCTL         ctl;
373
374         MemSet(&ctl, 0, sizeof(ctl));
375         ctl.keysize = sizeof(Oid);
376         ctl.entrysize = sizeof(TSConfigCacheEntry);
377         ctl.hash = oid_hash;
378         TSConfigCacheHash = hash_create("Tsearch configuration cache", 16,
379                                                                         &ctl, HASH_ELEM | HASH_FUNCTION);
380         /* Flush cache on pg_ts_config and pg_ts_config_map changes */
381         CacheRegisterSyscacheCallback(TSCONFIGOID, InvalidateTSCacheCallBack,
382                                                                   PointerGetDatum(TSConfigCacheHash));
383         CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
384                                                                   PointerGetDatum(TSConfigCacheHash));
385
386         /* Also make sure CacheMemoryContext exists */
387         if (!CacheMemoryContext)
388                 CreateCacheMemoryContext();
389 }
390
391 /*
392  * Fetch configuration cache entry
393  */
394 TSConfigCacheEntry *
395 lookup_ts_config_cache(Oid cfgId)
396 {
397         TSConfigCacheEntry *entry;
398
399         if (TSConfigCacheHash == NULL)
400         {
401                 /* First time through: initialize the hash table */
402                 init_ts_config_cache();
403         }
404
405         /* Check single-entry cache */
406         if (lastUsedConfig && lastUsedConfig->cfgId == cfgId &&
407                 lastUsedConfig->isvalid)
408                 return lastUsedConfig;
409
410         /* Try to look up an existing entry */
411         entry = (TSConfigCacheEntry *) hash_search(TSConfigCacheHash,
412                                                                                            (void *) &cfgId,
413                                                                                            HASH_FIND, NULL);
414         if (entry == NULL || !entry->isvalid)
415         {
416                 /*
417                  * If we didn't find one, we want to make one. But first look up the
418                  * object to be sure the OID is real.
419                  */
420                 HeapTuple       tp;
421                 Form_pg_ts_config cfg;
422                 Relation        maprel;
423                 Relation        mapidx;
424                 ScanKeyData mapskey;
425                 SysScanDesc mapscan;
426                 HeapTuple       maptup;
427                 ListDictionary maplists[MAXTOKENTYPE + 1];
428                 Oid                     mapdicts[MAXDICTSPERTT];
429                 int                     maxtokentype;
430                 int                     ndicts;
431                 int                     i;
432
433                 tp = SearchSysCache(TSCONFIGOID,
434                                                         ObjectIdGetDatum(cfgId),
435                                                         0, 0, 0);
436                 if (!HeapTupleIsValid(tp))
437                         elog(ERROR, "cache lookup failed for text search configuration %u",
438                                  cfgId);
439                 cfg = (Form_pg_ts_config) GETSTRUCT(tp);
440
441                 /*
442                  * Sanity checks
443                  */
444                 if (!OidIsValid(cfg->cfgparser))
445                         elog(ERROR, "text search configuration %u has no parser", cfgId);
446
447                 if (entry == NULL)
448                 {
449                         bool            found;
450
451                         /* Now make the cache entry */
452                         entry = (TSConfigCacheEntry *)
453                                 hash_search(TSConfigCacheHash,
454                                                         (void *) &cfgId,
455                                                         HASH_ENTER, &found);
456                         Assert(!found);         /* it wasn't there a moment ago */
457                 }
458                 else
459                 {
460                         /* Cleanup old contents */
461                         if (entry->map)
462                         {
463                                 for (i = 0; i < entry->lenmap; i++)
464                                         if (entry->map[i].dictIds)
465                                                 pfree(entry->map[i].dictIds);
466                                 pfree(entry->map);
467                         }
468                 }
469
470                 MemSet(entry, 0, sizeof(TSConfigCacheEntry));
471                 entry->cfgId = cfgId;
472                 entry->prsId = cfg->cfgparser;
473
474                 ReleaseSysCache(tp);
475
476                 /*
477                  * Scan pg_ts_config_map to gather dictionary list for each token type
478                  *
479                  * Because the index is on (mapcfg, maptokentype, mapseqno), we will
480                  * see the entries in maptokentype order, and in mapseqno order for
481                  * each token type, even though we didn't explicitly ask for that.
482                  */
483                 MemSet(maplists, 0, sizeof(maplists));
484                 maxtokentype = 0;
485                 ndicts = 0;
486
487                 ScanKeyInit(&mapskey,
488                                         Anum_pg_ts_config_map_mapcfg,
489                                         BTEqualStrategyNumber, F_OIDEQ,
490                                         ObjectIdGetDatum(cfgId));
491
492                 maprel = heap_open(TSConfigMapRelationId, AccessShareLock);
493                 mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
494                 mapscan = systable_beginscan_ordered(maprel, mapidx,
495                                                                                          SnapshotNow, 1, &mapskey);
496
497                 while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
498                 {
499                         Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
500                         int                     toktype = cfgmap->maptokentype;
501
502                         if (toktype <= 0 || toktype > MAXTOKENTYPE)
503                                 elog(ERROR, "maptokentype value %d is out of range", toktype);
504                         if (toktype < maxtokentype)
505                                 elog(ERROR, "maptokentype entries are out of order");
506                         if (toktype > maxtokentype)
507                         {
508                                 /* starting a new token type, but first save the prior data */
509                                 if (ndicts > 0)
510                                 {
511                                         maplists[maxtokentype].len = ndicts;
512                                         maplists[maxtokentype].dictIds = (Oid *)
513                                                 MemoryContextAlloc(CacheMemoryContext,
514                                                                                    sizeof(Oid) * ndicts);
515                                         memcpy(maplists[maxtokentype].dictIds, mapdicts,
516                                                    sizeof(Oid) * ndicts);
517                                 }
518                                 maxtokentype = toktype;
519                                 mapdicts[0] = cfgmap->mapdict;
520                                 ndicts = 1;
521                         }
522                         else
523                         {
524                                 /* continuing data for current token type */
525                                 if (ndicts >= MAXDICTSPERTT)
526                                         elog(ERROR, "too many pg_ts_config_map entries for one token type");
527                                 mapdicts[ndicts++] = cfgmap->mapdict;
528                         }
529                 }
530
531                 systable_endscan_ordered(mapscan);
532                 index_close(mapidx, AccessShareLock);
533                 heap_close(maprel, AccessShareLock);
534
535                 if (ndicts > 0)
536                 {
537                         /* save the last token type's dictionaries */
538                         maplists[maxtokentype].len = ndicts;
539                         maplists[maxtokentype].dictIds = (Oid *)
540                                 MemoryContextAlloc(CacheMemoryContext,
541                                                                    sizeof(Oid) * ndicts);
542                         memcpy(maplists[maxtokentype].dictIds, mapdicts,
543                                    sizeof(Oid) * ndicts);
544                         /* and save the overall map */
545                         entry->lenmap = maxtokentype + 1;
546                         entry->map = (ListDictionary *)
547                                 MemoryContextAlloc(CacheMemoryContext,
548                                                                    sizeof(ListDictionary) * entry->lenmap);
549                         memcpy(entry->map, maplists,
550                                    sizeof(ListDictionary) * entry->lenmap);
551                 }
552
553                 entry->isvalid = true;
554         }
555
556         lastUsedConfig = entry;
557
558         return entry;
559 }
560
561
562 /*---------------------------------------------------
563  * GUC variable "default_text_search_config"
564  *---------------------------------------------------
565  */
566
567 Oid
568 getTSCurrentConfig(bool emitError)
569 {
570         /* if we have a cached value, return it */
571         if (OidIsValid(TSCurrentConfigCache))
572                 return TSCurrentConfigCache;
573
574         /* fail if GUC hasn't been set up yet */
575         if (TSCurrentConfig == NULL || *TSCurrentConfig == '\0')
576         {
577                 if (emitError)
578                         elog(ERROR, "text search configuration isn't set");
579                 else
580                         return InvalidOid;
581         }
582
583         if (TSConfigCacheHash == NULL)
584         {
585                 /* First time through: initialize the tsconfig inval callback */
586                 init_ts_config_cache();
587         }
588
589         /* Look up the config */
590         TSCurrentConfigCache =
591                 TSConfigGetCfgid(stringToQualifiedNameList(TSCurrentConfig),
592                                                  !emitError);
593
594         return TSCurrentConfigCache;
595 }
596
597 const char *
598 assignTSCurrentConfig(const char *newval, bool doit, GucSource source)
599 {
600         /*
601          * If we aren't inside a transaction, we cannot do database access so
602          * cannot verify the config name.  Must accept it on faith.
603          */
604         if (IsTransactionState())
605         {
606                 Oid                     cfgId;
607                 HeapTuple       tuple;
608                 Form_pg_ts_config cfg;
609                 char       *buf;
610
611                 cfgId = TSConfigGetCfgid(stringToQualifiedNameList(newval), true);
612
613                 if (!OidIsValid(cfgId))
614                         return NULL;
615
616                 /*
617                  * Modify the actually stored value to be fully qualified, to ensure
618                  * later changes of search_path don't affect it.
619                  */
620                 tuple = SearchSysCache(TSCONFIGOID,
621                                                            ObjectIdGetDatum(cfgId),
622                                                            0, 0, 0);
623                 if (!HeapTupleIsValid(tuple))
624                         elog(ERROR, "cache lookup failed for text search configuration %u",
625                                  cfgId);
626                 cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
627
628                 buf = quote_qualified_identifier(get_namespace_name(cfg->cfgnamespace),
629                                                                                  NameStr(cfg->cfgname));
630
631                 ReleaseSysCache(tuple);
632
633                 /* GUC wants it malloc'd not palloc'd */
634                 newval = strdup(buf);
635                 pfree(buf);
636
637                 if (doit && newval)
638                         TSCurrentConfigCache = cfgId;
639         }
640         else
641         {
642                 if (doit)
643                         TSCurrentConfigCache = InvalidOid;
644         }
645
646         return newval;
647 }