diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b87b714 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.o +*.so +/log/ +/results/ +/tmp_check/ diff --git a/LICENSE b/LICENSE index 417dcbb..54e49a5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,25 +1,19 @@ -Copyright 2012, Tomas Vondra (tv@fuzzy.cz). All rights reserved. +Copyright (c) 2016-2018, Postgres Professional +Portions Copyright 2012, Tomas Vondra (tv@fuzzy.cz). All rights reserved. -Redistribution and use in source and binary forms, with or without modification, are -permitted provided that the following conditions are met: +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement +is hereby granted, provided that the above copyright notice and this +paragraph and the following two paragraphs appear in all copies. - 1. Redistributions of source code must retain the above copyright notice, this list of - conditions and the following disclaimer. +IN NO EVENT SHALL POSTGRES PROFESSIONAL BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING +LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +DOCUMENTATION, EVEN IF POSTGRES PROFESSIONAL HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. - 2. Redistributions in binary form must reproduce the above copyright notice, this list - of conditions and the following disclaimer in the documentation and/or other materials - provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY TOMAS VONDRA ''AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TOMAS VONDRA OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the -authors and should not be interpreted as representing official policies, either expressed -or implied, of Tomas Vondra. \ No newline at end of file +POSTGRES PROFESSIONAL SPECIFICALLY DISCLAIMS ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +ON AN "AS IS" BASIS, AND POSTGRES PROFESSIONAL HAS NO OBLIGATIONS TO +PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/Makefile b/Makefile index cf91b65..15d3187 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ OBJS = src/shared_ispell.o EXTENSION = shared_ispell DATA = shared_ispell--1.1.0.sql -REGRESS = shared_ispell +REGRESS = security shared_ispell EXTRA_REGRESS_OPTS=--temp-config=$(top_srcdir)/$(subdir)/postgresql.conf @@ -21,4 +21,3 @@ include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif -installcheck:; diff --git a/README.md b/README.md index b6d359e..9f9b6d8 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,23 @@ dictionary, this may save you a lot of resources. Install ------- -Installing the extension is quite simple, especially if you're on 9.1. -In that case all you need to do is this: - $ make install +Before build and install `shared_ispell` you should ensure following: + +* PostgreSQL version is 9.6 or later. + +Installing the extension is quite simple. In that case all you need to do is this: + + $ git clone git@github.com:postgrespro/shared_ispell.git + $ cd shared_ispell + $ make USE_PGXS=1 + $ make USE_PGXS=1 install and then (after connecting to the database) db=# CREATE EXTENSION shared_ispell; -If you're on pre-9.1 version, you'll have to do the second part manually -by running the SQL script (shared_ispell--x.y.sql) in the database. If -needed, replace MODULE_PATHNAME by $libdir. +> **Important:** Don't forget to set the `PG_CONFIG` variable in case you want to test `shared_ispell` on a custom build of PostgreSQL. Read more [here](https://wiki.postgresql.org/wiki/Building_and_Installing_PostgreSQL_Extension_Modules). Config diff --git a/expected/security.out b/expected/security.out new file mode 100644 index 0000000..6f73aa1 --- /dev/null +++ b/expected/security.out @@ -0,0 +1,40 @@ +create type si_dicts_result as (dict_name VARCHAR, affix_name VARCHAR, words INT, affixes INT, bytes INT); +create function shared_ispell_dicts( OUT dict_name VARCHAR, OUT affix_name VARCHAR, OUT words INT, OUT affixes INT, OUT bytes INT) +returns SETOF record as $$ +declare + qString varchar(4000); + rec si_dicts_result; +begin + qString := 'select * from shared_ispell_dicts()'; + for rec in execute qString loop + return NEXT; + end loop; + return; +end +$$ language plpgsql; +create extension shared_ispell; +ERROR: function "shared_ispell_dicts" already exists with same argument types +drop extension if exists shared_ispell; +NOTICE: extension "shared_ispell" does not exist, skipping +drop type si_dicts_result; +drop function shared_ispell_dicts(); +create type si_stoplists_result as (stop_name VARCHAR, words INT, bytes INT); +create function shared_ispell_stoplists(OUT stop_name VARCHAR, OUT words INT, OUT bytes INT) +returns SETOF record as $$ +declare + rec si_stoplists_result; + qString varchar(4000); +begin + qString := 'select * from shared_ispell_stoplists()'; + for rec in execute qString loop + return NEXT; + end loop; + return; +end +$$ language plpgsql; +create extension shared_ispell; +ERROR: function "shared_ispell_stoplists" already exists with same argument types +drop extension if exists shared_ispell; +NOTICE: extension "shared_ispell" does not exist, skipping +drop type si_stoplists_result; +drop function shared_ispell_stoplists(); diff --git a/expected/shared_ispell.out b/expected/shared_ispell.out index 68e59c9..9998cb9 100644 --- a/expected/shared_ispell.out +++ b/expected/shared_ispell.out @@ -211,3 +211,9 @@ SELECT ts_lexize('shared_hunspell', 'skies'); {sky} (1 row) +SELECT ts_lexize('shared_hunspell', 'skies'); + ts_lexize +----------- + {sky} +(1 row) + diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..0f07821 --- /dev/null +++ b/meson.build @@ -0,0 +1,39 @@ +# Copyright (c) 2025, Postgres Professional + +# Does not support the PGXS infrastructure at this time. Please, compile as part +# of the contrib source tree. + +shared_ispell_sources = files( + 'src' / 'shared_ispell.c' +) + +if host_system == 'windows' + shared_ispell_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'shared_ispell', + '--FILEDESC', 'shared_ispell - provides a shared ispell dictionary, i.e. a dictionary that\'s stored in shared segment.',]) +endif + +shared_ispell = shared_module('shared_ispell', + shared_ispell_sources, + kwargs: contrib_mod_args, +) +contrib_targets += shared_ispell + +install_data( + 'shared_ispell.control', + 'shared_ispell--1.1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'shared_ispell', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'security', + 'shared_ispell', + ], + 'regress_args': ['--temp-config', files('postgresql.conf')], + }, +} diff --git a/shared_ispell--1.1.0.sql b/shared_ispell--1.1.0.sql index 07c3ac3..7f638ab 100644 --- a/shared_ispell--1.1.0.sql +++ b/shared_ispell--1.1.0.sql @@ -1,34 +1,34 @@ -CREATE OR REPLACE FUNCTION shared_ispell_init(internal) +CREATE FUNCTION shared_ispell_init(internal) RETURNS internal AS 'MODULE_PATHNAME', 'dispell_init' LANGUAGE C IMMUTABLE; -CREATE OR REPLACE FUNCTION shared_ispell_lexize(internal,internal,internal,internal) +CREATE FUNCTION shared_ispell_lexize(internal,internal,internal,internal) RETURNS internal AS 'MODULE_PATHNAME', 'dispell_lexize' LANGUAGE C IMMUTABLE; -CREATE OR REPLACE FUNCTION shared_ispell_reset() +CREATE FUNCTION shared_ispell_reset() RETURNS void AS 'MODULE_PATHNAME', 'dispell_reset' LANGUAGE C IMMUTABLE; -CREATE OR REPLACE FUNCTION shared_ispell_mem_used() +CREATE FUNCTION shared_ispell_mem_used() RETURNS integer AS 'MODULE_PATHNAME', 'dispell_mem_used' LANGUAGE C IMMUTABLE; -CREATE OR REPLACE FUNCTION shared_ispell_mem_available() +CREATE FUNCTION shared_ispell_mem_available() RETURNS integer AS 'MODULE_PATHNAME', 'dispell_mem_available' LANGUAGE C IMMUTABLE; -CREATE OR REPLACE FUNCTION shared_ispell_dicts( OUT dict_name VARCHAR, OUT affix_name VARCHAR, OUT words INT, OUT affixes INT, OUT bytes INT) +CREATE FUNCTION shared_ispell_dicts( OUT dict_name VARCHAR, OUT affix_name VARCHAR, OUT words INT, OUT affixes INT, OUT bytes INT) RETURNS SETOF record AS 'MODULE_PATHNAME', 'dispell_list_dicts' LANGUAGE C IMMUTABLE; -CREATE OR REPLACE FUNCTION shared_ispell_stoplists( OUT stop_name VARCHAR, OUT words INT, OUT bytes INT) +CREATE FUNCTION shared_ispell_stoplists( OUT stop_name VARCHAR, OUT words INT, OUT bytes INT) RETURNS SETOF record AS 'MODULE_PATHNAME', 'dispell_list_stoplists' LANGUAGE C IMMUTABLE; diff --git a/sql/security.sql b/sql/security.sql new file mode 100644 index 0000000..33a09e1 --- /dev/null +++ b/sql/security.sql @@ -0,0 +1,43 @@ +create type si_dicts_result as (dict_name VARCHAR, affix_name VARCHAR, words INT, affixes INT, bytes INT); + +create function shared_ispell_dicts( OUT dict_name VARCHAR, OUT affix_name VARCHAR, OUT words INT, OUT affixes INT, OUT bytes INT) +returns SETOF record as $$ +declare + qString varchar(4000); + rec si_dicts_result; +begin + qString := 'select * from shared_ispell_dicts()'; + for rec in execute qString loop + return NEXT; + end loop; + return; +end +$$ language plpgsql; + +create extension shared_ispell; + +drop extension if exists shared_ispell; +drop type si_dicts_result; +drop function shared_ispell_dicts(); + +create type si_stoplists_result as (stop_name VARCHAR, words INT, bytes INT); + +create function shared_ispell_stoplists(OUT stop_name VARCHAR, OUT words INT, OUT bytes INT) +returns SETOF record as $$ +declare + rec si_stoplists_result; + qString varchar(4000); +begin + qString := 'select * from shared_ispell_stoplists()'; + for rec in execute qString loop + return NEXT; + end loop; + return; +end +$$ language plpgsql; + +create extension shared_ispell; + +drop extension if exists shared_ispell; +drop type si_stoplists_result; +drop function shared_ispell_stoplists(); diff --git a/sql/shared_ispell.sql b/sql/shared_ispell.sql index e791399..0a4af97 100644 --- a/sql/shared_ispell.sql +++ b/sql/shared_ispell.sql @@ -53,4 +53,5 @@ SELECT stop_name, words FROM shared_ispell_stoplists(); SELECT shared_ispell_reset(); SELECT ts_lexize('shared_ispell', 'skies'); -SELECT ts_lexize('shared_hunspell', 'skies'); \ No newline at end of file +SELECT ts_lexize('shared_hunspell', 'skies'); +SELECT ts_lexize('shared_hunspell', 'skies'); diff --git a/src/shared_ispell.c b/src/shared_ispell.c index 126dd72..37243e2 100644 --- a/src/shared_ispell.c +++ b/src/shared_ispell.c @@ -20,33 +20,33 @@ * ===== shared segment init (postmaster startup) ===== * * _PG_init - * -> ispell_shmem_startup (registered as a hook) + * -> ispell_shmem_startup (registered as a hook) * * ===== dictionary init (backend) ===== * * dispell_init - * -> init_shared_dict - * -> get_shared_dict - * -> NIStartBuild - * -> NIImportDictionary - * -> NIImportAffixes - * -> NISortDictionary - * -> NISortAffixes - * -> NIFinishBuild - * -> sizeIspellDict - * -> copyIspellDict - * -> copySPNode - * -> get_shared_stop_list - * -> readstoplist - * -> copyStopList + * -> init_shared_dict + * -> get_shared_dict + * -> NIStartBuild + * -> NIImportDictionary + * -> NIImportAffixes + * -> NISortDictionary + * -> NISortAffixes + * -> NIFinishBuild + * -> sizeIspellDict + * -> copyIspellDict + * -> copySPNode + * -> get_shared_stop_list + * -> readstoplist + * -> copyStopList * * ===== dictionary reinit after reset (backend) ===== * * dispell_lexize - * -> timestamp of lookup < last reset - * -> init_shared_dict - * (see dispell_init above) - * -> SharedNINormalizeWord + * -> timestamp of lookup < last reset + * -> init_shared_dict + * (see dispell_init above) + * -> SharedNINormalizeWord */ #include "postgres.h" @@ -59,6 +59,7 @@ #include "access/htup_details.h" #include "funcapi.h" #include "utils/builtins.h" +#include "utils/guc.h" #include "shared_ispell.h" #include "tsearch/dicts/spell.h" @@ -66,12 +67,11 @@ PG_MODULE_MAGIC; void _PG_init(void); -void _PG_fini(void); /* Memory for dictionaries in kbytes */ static int max_ispell_mem_size_kb; -/* Saved hook values in case of unload */ +/* Saved hook value for proper chaining */ static shmem_startup_hook_type prev_shmem_startup_hook = NULL; /* These are used to allocate data within shared segment */ @@ -94,9 +94,14 @@ static int sizeStopList(StopList *list, char *stopFile); static Size max_ispell_mem_size() { - return (Size)max_ispell_mem_size_kb * 1024L; + return (Size) max_ispell_mem_size_kb * 1024L; } +#if (PG_VERSION_NUM >= 150000) +static shmem_request_hook_type prev_shmem_request_hook = NULL; +static void shared_ispell_shmem_request(void); +#endif + /* * Module load callback */ @@ -127,34 +132,36 @@ _PG_init(void) EmitWarningsOnPlaceholders("shared_ispell"); - /* - * Request additional shared resources. (These are no-ops if we're not in - * the postmaster process.) We'll allocate or attach to the shared - * resources in ispell_shmem_startup(). - */ +#if PG_VERSION_NUM >= 150000 + prev_shmem_request_hook = shmem_request_hook; + shmem_request_hook = shared_ispell_shmem_request; +#else RequestAddinShmemSpace(max_ispell_mem_size()); - #if PG_VERSION_NUM >= 90600 +#if PG_VERSION_NUM >= 90600 RequestNamedLWLockTranche("shared_ispell", 1); - #else +#else RequestAddinLWLocks(1); - #endif +#endif +#endif /* Install hooks. */ prev_shmem_startup_hook = shmem_startup_hook; shmem_startup_hook = ispell_shmem_startup; } - -/* - * Module unload callback - */ -void -_PG_fini(void) +#if PG_VERSION_NUM >= 150000 +static void +shared_ispell_shmem_request(void) { - /* Uninstall hooks. */ - shmem_startup_hook = prev_shmem_startup_hook; + if (prev_shmem_request_hook) + prev_shmem_request_hook(); + + RequestAddinShmemSpace(max_ispell_mem_size()); + + RequestNamedLWLockTranche("shared_ispell", 1); } +#endif /* * Probably the most important part of the startup - initializes the @@ -166,8 +173,8 @@ _PG_fini(void) static void ispell_shmem_startup() { - bool found = FALSE; - char *segment; + bool found = false; + char *segment; if (prev_shmem_startup_hook) prev_shmem_startup_hook(); @@ -177,9 +184,7 @@ ispell_shmem_startup() */ LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); - segment = ShmemInitStruct(SEGMENT_NAME, - max_ispell_mem_size(), - &found); + segment = ShmemInitStruct(SEGMENT_NAME, max_ispell_mem_size(), &found); segment_info = (SegmentInfo *) segment; /* Was the shared memory segment already initialized? */ @@ -187,16 +192,16 @@ ispell_shmem_startup() { memset(segment, 0, max_ispell_mem_size()); - #if PG_VERSION_NUM >= 90600 +#if PG_VERSION_NUM >= 90600 segment_info->lock = &(GetNamedLWLockTranche("shared_ispell"))->lock; - #else +#else segment_info->lock = LWLockAssign(); - #endif +#endif segment_info->firstfree = segment + MAXALIGN(sizeof(SegmentInfo)); - segment_info->available = max_ispell_mem_size() - - (int)(segment_info->firstfree - segment); + segment_info->available = max_ispell_mem_size() - + (int) (segment_info->firstfree - segment); - segment_info->lastReset = GetCurrentTimestamp(); + INSTR_TIME_SET_CURRENT(segment_info->lastReset); } LWLockRelease(AddinShmemInitLock); @@ -285,15 +290,15 @@ clean_dict_affix(IspellDict *dict) * of the shared memory (using SegmentInfo->lock). */ static void -init_shared_dict(DictInfo *info, char *dictFile, char *affFile, char *stopFile) +init_shared_dict(DictInfo *info, MemoryContext infoCntx, + char *dictFile, char *affFile, char *stopFile) { - int size; - + int size; SharedIspellDict *shdict = NULL; - SharedStopList *shstop = NULL; + SharedStopList *shstop = NULL; + MemoryContext oldctx; - IspellDict *dict; - StopList stoplist; + oldctx = MemoryContextSwitchTo(infoCntx); /* DICTIONARY + AFFIXES */ @@ -313,11 +318,14 @@ init_shared_dict(DictInfo *info, char *dictFile, char *affFile, char *stopFile) /* load the dictionary (word list) if not yet defined */ if (shdict == NULL) { + IspellDict *dict; + dict = (IspellDict *) palloc0(sizeof(IspellDict)); NIStartBuild(dict); NIImportDictionary(dict, get_tsearch_config_filename(dictFile, "dict")); + dict->flagMode = info->dict.flagMode; dict->usecompound = info->dict.usecompound; dict->nCompoundAffixFlag = dict->mCompoundAffixFlag = @@ -333,11 +341,13 @@ init_shared_dict(DictInfo *info, char *dictFile, char *affFile, char *stopFile) */ if (info->dict.useFlagAliases) { - int i; + int i; + dict->useFlagAliases = true; dict->lenAffixData = info->dict.lenAffixData; dict->nAffixData = info->dict.nAffixData; dict->AffixData = (char **) palloc0(dict->nAffixData * sizeof(char *)); + for (i = 0; i < dict->nAffixData; i++) { dict->AffixData[i] = palloc0(strlen(info->dict.AffixData[i]) + 1); @@ -383,6 +393,8 @@ init_shared_dict(DictInfo *info, char *dictFile, char *affFile, char *stopFile) /* load the stopwords if not yet defined */ if (shstop == NULL) { + StopList stoplist; + readstoplist(stopFile, &stoplist, lowerstr); size = sizeStopList(&stoplist, stopFile); @@ -404,23 +416,19 @@ init_shared_dict(DictInfo *info, char *dictFile, char *affFile, char *stopFile) info->shdict = shdict; info->shstop = shstop; - info->lookup = GetCurrentTimestamp(); + INSTR_TIME_SET_CURRENT(info->lookup); memcpy(info->dictFile, dictFile, strlen(dictFile) + 1); - memcpy(info->affixFile, dictFile, strlen(affFile)+ 1); + memcpy(info->affixFile, affFile, strlen(affFile) + 1); if (stopFile != NULL) - memcpy(info->stopFile, dictFile, strlen(stopFile) + 1); + memcpy(info->stopFile, stopFile, strlen(stopFile) + 1); else memset(info->stopFile, 0, sizeof(info->stopFile)); -} -Datum dispell_init(PG_FUNCTION_ARGS); -Datum dispell_lexize(PG_FUNCTION_ARGS); -Datum dispell_reset(PG_FUNCTION_ARGS); -Datum dispell_mem_available(PG_FUNCTION_ARGS); -Datum dispell_mem_used(PG_FUNCTION_ARGS); -Datum dispell_list_dicts(PG_FUNCTION_ARGS); -Datum dispell_list_stoplists(PG_FUNCTION_ARGS); + MemoryContextSwitchTo(oldctx); + /* save current context as long-lived */ + info->infoCntx = infoCntx; +} PG_FUNCTION_INFO_V1(dispell_init); PG_FUNCTION_INFO_V1(dispell_lexize); @@ -443,9 +451,10 @@ dispell_reset(PG_FUNCTION_ARGS) segment_info->shdict = NULL; segment_info->shstop = NULL; - segment_info->lastReset = GetCurrentTimestamp(); + INSTR_TIME_SET_CURRENT(segment_info->lastReset); segment_info->firstfree = ((char*) segment_info) + MAXALIGN(sizeof(SegmentInfo)); - segment_info->available = max_ispell_mem_size() - (int)(segment_info->firstfree - (char*) segment_info); + segment_info->available = max_ispell_mem_size() - + (int) (segment_info->firstfree - (char*) segment_info); memset(segment_info->firstfree, 0, segment_info->available); @@ -471,12 +480,14 @@ dispell_mem_available(PG_FUNCTION_ARGS) } /* - * Returns amount of 'occupied space' in the shared segment (used by current dictionaries). + * Returns amount of 'occupied space' in the shared segment (used by current + * dictionaries). */ Datum dispell_mem_used(PG_FUNCTION_ARGS) { - int result = 0; + int result = 0; + LWLockAcquire(segment_info->lock, LW_SHARED); result = max_ispell_mem_size() - segment_info->available; @@ -498,6 +509,9 @@ dispell_mem_used(PG_FUNCTION_ARGS) * The StopWords parameter is optional, the two other are required. * * If any of the filenames are incorrect, the call to init_shared_dict will fail. + * + * Do not call it directly - it saves current memory context as long-lived + * context. */ Datum dispell_init(PG_FUNCTION_ARGS) @@ -570,7 +584,15 @@ dispell_init(PG_FUNCTION_ARGS) /* search if the dictionary is already initialized */ LWLockAcquire(segment_info->lock, LW_EXCLUSIVE); - init_shared_dict(info, dictFile, affFile, stopFile); + /* + * Current context is a long lived context. Create child context to store + * DictInfo internal data. + */ + info->infoCntx = AllocSetContextCreate(CurrentMemoryContext, + "shared_ispell context", + ALLOCSET_DEFAULT_SIZES); + + init_shared_dict(info, info->infoCntx, dictFile, affFile, stopFile); LWLockRelease(segment_info->lock); @@ -586,7 +608,7 @@ dispell_lexize(PG_FUNCTION_ARGS) char *txt; TSLexeme *res; TSLexeme *ptr, - *cptr; + *cptr; if (len <= 0) PG_RETURN_POINTER(NULL); @@ -597,13 +619,25 @@ dispell_lexize(PG_FUNCTION_ARGS) LWLockAcquire(segment_info->lock, LW_SHARED); /* do we need to reinit the dictionary? was the dict reset since the lookup */ - if (timestamp_cmp_internal(info->lookup, segment_info->lastReset) < 0) + if (INSTR_TIME_GET_MICROSEC(info->lookup) < + INSTR_TIME_GET_MICROSEC(segment_info->lastReset)) { + DictInfo saveInfo = *info; + /* relock in exclusive mode */ LWLockRelease(segment_info->lock); LWLockAcquire(segment_info->lock, LW_EXCLUSIVE); - init_shared_dict(info, info->dictFile, info->affixFile, info->stopFile); + /* + * info is allocated in info->saveCntx, so that's why we use a copy of + * info here + */ + + MemoryContextReset(saveInfo.infoCntx); + MemSet(info, 0, sizeof(*info)); + + init_shared_dict(info, saveInfo.infoCntx, saveInfo.dictFile, + saveInfo.affixFile, saveInfo.stopFile); } res = NINormalizeWord(&(info->dict), txt); @@ -649,7 +683,8 @@ dispell_lexize(PG_FUNCTION_ARGS) static char * shalloc(int bytes) { - char *result; + char *result; + bytes = MAXALIGN(bytes); /* This shouldn't really happen, as the init_shared_dict checks the size @@ -676,8 +711,10 @@ shalloc(int bytes) static char * shstrcpy(char *str) { - char *tmp = shalloc(strlen(str) + 1); + char *tmp = shalloc(strlen(str) + 1); + memcpy(tmp, str, strlen(str) + 1); + return tmp; } @@ -693,17 +730,17 @@ shstrcpy(char *str) static SPNode * copySPNode(SPNode *node) { - int i; - SPNode *copy = NULL; + int i; + SPNode *copy = NULL; if (node == NULL) - return NULL; + return NULL; copy = (SPNode *) shalloc(offsetof(SPNode, data) + sizeof(SPNodeData) * node->length); memcpy(copy, node, offsetof(SPNode, data) + sizeof(SPNodeData) * node->length); for (i = 0; i < node->length; i++) - copy->data[i].node = copySPNode(node->data[i].node); + copy->data[i].node = copySPNode(node->data[i].node); return copy; } @@ -711,11 +748,11 @@ copySPNode(SPNode *node) static int sizeSPNode(SPNode *node) { - int i; - int size = 0; + int i; + int size = 0; if (node == NULL) - return 0; + return 0; size = MAXALIGN(offsetof(SPNode, data) + sizeof(SPNodeData) * node->length); @@ -771,8 +808,7 @@ sizeStopList(StopList *list, char *stopFile) static SharedIspellDict * copyIspellDict(IspellDict *dict, char *dictFile, char *affixFile, int size, int words) { - int i; - + int i; SharedIspellDict *copy = (SharedIspellDict *) shalloc(sizeof(SharedIspellDict)); copy->dictFile = shalloc(strlen(dictFile) + 1); @@ -789,6 +825,8 @@ copyIspellDict(IspellDict *dict, char *dictFile, char *affixFile, int size, int for (i = 0; i < copy->dict.nAffixData; i++) copy->dict.AffixData[i] = shstrcpy(dict->AffixData[i]); + copy->dict.flagMode = dict->flagMode; + copy->nbytes = size; copy->nwords = words; @@ -804,8 +842,8 @@ copyIspellDict(IspellDict *dict, char *dictFile, char *affixFile, int size, int static int sizeIspellDict(IspellDict *dict, char *dictFile, char *affixFile) { - int i; - int size = MAXALIGN(sizeof(SharedIspellDict)); + int i; + int size = MAXALIGN(sizeof(SharedIspellDict)); size += MAXALIGN(strlen(dictFile) + 1); size += MAXALIGN(strlen(affixFile) + 1); @@ -815,7 +853,7 @@ sizeIspellDict(IspellDict *dict, char *dictFile, char *affixFile) /* copy affix data */ size += MAXALIGN(sizeof(char *) * dict->nAffixData); for (i = 0; i < dict->nAffixData; i++) - size += MAXALIGN(sizeof(char) * strlen(dict->AffixData[i]) + 1); + size += MAXALIGN(sizeof(char) * strlen(dict->AffixData[i]) + 1); return size; } @@ -824,9 +862,9 @@ sizeIspellDict(IspellDict *dict, char *dictFile, char *affixFile) Datum dispell_list_dicts(PG_FUNCTION_ARGS) { - FuncCallContext *funcctx; - TupleDesc tupdesc; - SharedIspellDict *dict; + FuncCallContext *funcctx; + TupleDesc tupdesc; + SharedIspellDict *dict; /* init on the first call */ if (SRF_IS_FIRSTCALL()) @@ -842,10 +880,10 @@ dispell_list_dicts(PG_FUNCTION_ARGS) /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("function returning record called in context " - "that cannot accept type record"))); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); /* * generate attribute metadata needed later to produce tuples from raw diff --git a/src/shared_ispell.h b/src/shared_ispell.h index 92de330..cbba198 100644 --- a/src/shared_ispell.h +++ b/src/shared_ispell.h @@ -2,6 +2,7 @@ #define __SHARED_ISPELL_H__ #include "storage/lwlock.h" +#include "utils/memutils.h" #include "utils/timestamp.h" #include "tsearch/dicts/spell.h" #include "tsearch/ts_public.h" @@ -41,9 +42,9 @@ typedef struct SharedStopList typedef struct SegmentInfo { LWLockId lock; - char *firstfree; /* first free address (always maxaligned) */ - size_t available; /* free space remaining at firstfree */ - Timestamp lastReset; /* last reset of the dictionary */ + char *firstfree; /* first free address (always maxaligned) */ + size_t available; /* free space remaining at firstfree */ + instr_time lastReset; /* last reset of the dictionary */ /* the shared segment (info and data) */ SharedIspellDict *shdict; @@ -53,7 +54,7 @@ typedef struct SegmentInfo /* used to keep track of dictionary in each backend */ typedef struct DictInfo { - Timestamp lookup; + instr_time lookup; char dictFile[MAXLEN]; char affixFile[MAXLEN]; @@ -66,6 +67,9 @@ typedef struct DictInfo SharedIspellDict *shdict; IspellDict dict; SharedStopList *shstop; + + /* MemoryContext of dict local content */ + MemoryContext infoCntx; } DictInfo; -#endif \ No newline at end of file +#endif