Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * dict_xsyn.c
4 : * Extended synonym dictionary
5 : *
6 : * Copyright (c) 2007-2025, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * contrib/dict_xsyn/dict_xsyn.c
10 : *
11 : *-------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 :
15 : #include <ctype.h>
16 :
17 : #include "catalog/pg_collation_d.h"
18 : #include "commands/defrem.h"
19 : #include "tsearch/ts_locale.h"
20 : #include "tsearch/ts_public.h"
21 : #include "utils/formatting.h"
22 :
23 2 : PG_MODULE_MAGIC_EXT(
24 : .name = "dict_xsyn",
25 : .version = PG_VERSION
26 : );
27 :
28 : typedef struct
29 : {
30 : char *key; /* Word */
31 : char *value; /* Unparsed list of synonyms, including the
32 : * word itself */
33 : } Syn;
34 :
35 : typedef struct
36 : {
37 : int len;
38 : Syn *syn;
39 :
40 : bool matchorig;
41 : bool keeporig;
42 : bool matchsynonyms;
43 : bool keepsynonyms;
44 : } DictSyn;
45 :
46 :
47 4 : PG_FUNCTION_INFO_V1(dxsyn_init);
48 4 : PG_FUNCTION_INFO_V1(dxsyn_lexize);
49 :
50 : static char *
51 298 : find_word(char *in, char **end)
52 : {
53 : char *start;
54 :
55 298 : *end = NULL;
56 410 : while (*in && isspace((unsigned char) *in))
57 112 : in += pg_mblen(in);
58 :
59 298 : if (!*in || *in == '#')
60 168 : return NULL;
61 130 : start = in;
62 :
63 824 : while (*in && !isspace((unsigned char) *in))
64 694 : in += pg_mblen(in);
65 :
66 130 : *end = in;
67 :
68 130 : return start;
69 : }
70 :
71 : static int
72 148 : compare_syn(const void *a, const void *b)
73 : {
74 148 : return strcmp(((const Syn *) a)->key, ((const Syn *) b)->key);
75 : }
76 :
77 : static void
78 28 : read_dictionary(DictSyn *d, const char *filename)
79 : {
80 28 : char *real_filename = get_tsearch_config_filename(filename, "rules");
81 : tsearch_readline_state trst;
82 : char *line;
83 28 : int cur = 0;
84 :
85 28 : if (!tsearch_readline_begin(&trst, real_filename))
86 0 : ereport(ERROR,
87 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
88 : errmsg("could not open synonym file \"%s\": %m",
89 : real_filename)));
90 :
91 196 : while ((line = tsearch_readline(&trst)) != NULL)
92 : {
93 : char *value;
94 : char *key;
95 : char *pos;
96 : char *end;
97 :
98 168 : if (*line == '\0')
99 0 : continue;
100 :
101 168 : value = str_tolower(line, strlen(line), DEFAULT_COLLATION_OID);
102 168 : pfree(line);
103 :
104 168 : pos = value;
105 232 : while ((key = find_word(pos, &end)) != NULL)
106 : {
107 : /* Enlarge syn structure if full */
108 76 : if (cur == d->len)
109 : {
110 28 : d->len = (d->len > 0) ? 2 * d->len : 16;
111 28 : if (d->syn)
112 0 : d->syn = (Syn *) repalloc(d->syn, sizeof(Syn) * d->len);
113 : else
114 28 : d->syn = (Syn *) palloc(sizeof(Syn) * d->len);
115 : }
116 :
117 : /* Save first word only if we will match it */
118 76 : if (pos != value || d->matchorig)
119 : {
120 68 : d->syn[cur].key = pnstrdup(key, end - key);
121 68 : d->syn[cur].value = pstrdup(value);
122 :
123 68 : cur++;
124 : }
125 :
126 76 : pos = end;
127 :
128 : /* Don't bother scanning synonyms if we will not match them */
129 76 : if (!d->matchsynonyms)
130 12 : break;
131 : }
132 :
133 168 : pfree(value);
134 : }
135 :
136 28 : tsearch_readline_end(&trst);
137 :
138 28 : d->len = cur;
139 28 : if (cur > 1)
140 16 : qsort(d->syn, d->len, sizeof(Syn), compare_syn);
141 :
142 28 : pfree(real_filename);
143 28 : }
144 :
145 : Datum
146 30 : dxsyn_init(PG_FUNCTION_ARGS)
147 : {
148 30 : List *dictoptions = (List *) PG_GETARG_POINTER(0);
149 : DictSyn *d;
150 : ListCell *l;
151 30 : char *filename = NULL;
152 :
153 30 : d = (DictSyn *) palloc0(sizeof(DictSyn));
154 30 : d->len = 0;
155 30 : d->syn = NULL;
156 30 : d->matchorig = true;
157 30 : d->keeporig = true;
158 30 : d->matchsynonyms = false;
159 30 : d->keepsynonyms = true;
160 :
161 170 : foreach(l, dictoptions)
162 : {
163 140 : DefElem *defel = (DefElem *) lfirst(l);
164 :
165 140 : if (strcmp(defel->defname, "matchorig") == 0)
166 : {
167 28 : d->matchorig = defGetBoolean(defel);
168 : }
169 112 : else if (strcmp(defel->defname, "keeporig") == 0)
170 : {
171 28 : d->keeporig = defGetBoolean(defel);
172 : }
173 84 : else if (strcmp(defel->defname, "matchsynonyms") == 0)
174 : {
175 28 : d->matchsynonyms = defGetBoolean(defel);
176 : }
177 56 : else if (strcmp(defel->defname, "keepsynonyms") == 0)
178 : {
179 28 : d->keepsynonyms = defGetBoolean(defel);
180 : }
181 28 : else if (strcmp(defel->defname, "rules") == 0)
182 : {
183 : /* we can't read the rules before parsing all options! */
184 28 : filename = defGetString(defel);
185 : }
186 : else
187 : {
188 0 : ereport(ERROR,
189 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
190 : errmsg("unrecognized xsyn parameter: \"%s\"",
191 : defel->defname)));
192 : }
193 : }
194 :
195 30 : if (filename)
196 28 : read_dictionary(d, filename);
197 :
198 30 : PG_RETURN_POINTER(d);
199 : }
200 :
201 : Datum
202 42 : dxsyn_lexize(PG_FUNCTION_ARGS)
203 : {
204 42 : DictSyn *d = (DictSyn *) PG_GETARG_POINTER(0);
205 42 : char *in = (char *) PG_GETARG_POINTER(1);
206 42 : int length = PG_GETARG_INT32(2);
207 : Syn word;
208 : Syn *found;
209 42 : TSLexeme *res = NULL;
210 :
211 42 : if (!length || d->len == 0)
212 6 : PG_RETURN_POINTER(NULL);
213 :
214 : /* Create search pattern */
215 : {
216 36 : char *temp = pnstrdup(in, length);
217 :
218 36 : word.key = str_tolower(temp, length, DEFAULT_COLLATION_OID);
219 36 : pfree(temp);
220 36 : word.value = NULL;
221 : }
222 :
223 : /* Look for matching syn */
224 36 : found = (Syn *) bsearch(&word, d->syn, d->len, sizeof(Syn), compare_syn);
225 36 : pfree(word.key);
226 :
227 36 : if (!found)
228 18 : PG_RETURN_POINTER(NULL);
229 :
230 : /* Parse string of synonyms and return array of words */
231 : {
232 18 : char *value = found->value;
233 : char *syn;
234 : char *pos;
235 : char *end;
236 18 : int nsyns = 0;
237 :
238 18 : res = palloc(sizeof(TSLexeme));
239 :
240 18 : pos = value;
241 66 : while ((syn = find_word(pos, &end)) != NULL)
242 : {
243 54 : res = repalloc(res, sizeof(TSLexeme) * (nsyns + 2));
244 :
245 : /* The first word is output only if keeporig=true */
246 54 : if (pos != value || d->keeporig)
247 : {
248 44 : res[nsyns].lexeme = pnstrdup(syn, end - syn);
249 44 : res[nsyns].nvariant = 0;
250 44 : res[nsyns].flags = 0;
251 44 : nsyns++;
252 : }
253 :
254 54 : pos = end;
255 :
256 : /* Stop if we are not to output the synonyms */
257 54 : if (!d->keepsynonyms)
258 6 : break;
259 : }
260 18 : res[nsyns].lexeme = NULL;
261 : }
262 :
263 18 : PG_RETURN_POINTER(res);
264 : }
|