Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * dict_xsyn.c
4 : * Extended synonym dictionary
5 : *
6 : * Copyright (c) 2007-2026, 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 1 : 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 2 : PG_FUNCTION_INFO_V1(dxsyn_init);
48 2 : PG_FUNCTION_INFO_V1(dxsyn_lexize);
49 :
50 : static char *
51 149 : find_word(char *in, char **end)
52 : {
53 : char *start;
54 :
55 149 : *end = NULL;
56 205 : while (*in && isspace((unsigned char) *in))
57 56 : in += pg_mblen_cstr(in);
58 :
59 149 : if (!*in || *in == '#')
60 84 : return NULL;
61 65 : start = in;
62 :
63 412 : while (*in && !isspace((unsigned char) *in))
64 347 : in += pg_mblen_cstr(in);
65 :
66 65 : *end = in;
67 :
68 65 : return start;
69 : }
70 :
71 : static int
72 74 : compare_syn(const void *a, const void *b)
73 : {
74 74 : return strcmp(((const Syn *) a)->key, ((const Syn *) b)->key);
75 : }
76 :
77 : static void
78 14 : read_dictionary(DictSyn *d, const char *filename)
79 : {
80 14 : char *real_filename = get_tsearch_config_filename(filename, "rules");
81 : tsearch_readline_state trst;
82 : char *line;
83 14 : int cur = 0;
84 :
85 14 : 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 98 : while ((line = tsearch_readline(&trst)) != NULL)
92 : {
93 : char *value;
94 : char *key;
95 : char *pos;
96 : char *end;
97 :
98 84 : if (*line == '\0')
99 0 : continue;
100 :
101 84 : value = str_tolower(line, strlen(line), DEFAULT_COLLATION_OID);
102 84 : pfree(line);
103 :
104 84 : pos = value;
105 116 : while ((key = find_word(pos, &end)) != NULL)
106 : {
107 : /* Enlarge syn structure if full */
108 38 : if (cur == d->len)
109 : {
110 14 : d->len = (d->len > 0) ? 2 * d->len : 16;
111 14 : if (d->syn)
112 0 : d->syn = repalloc_array(d->syn, Syn, d->len);
113 : else
114 14 : d->syn = palloc_array(Syn, d->len);
115 : }
116 :
117 : /* Save first word only if we will match it */
118 38 : if (pos != value || d->matchorig)
119 : {
120 34 : d->syn[cur].key = pnstrdup(key, end - key);
121 34 : d->syn[cur].value = pstrdup(value);
122 :
123 34 : cur++;
124 : }
125 :
126 38 : pos = end;
127 :
128 : /* Don't bother scanning synonyms if we will not match them */
129 38 : if (!d->matchsynonyms)
130 6 : break;
131 : }
132 :
133 84 : pfree(value);
134 : }
135 :
136 14 : tsearch_readline_end(&trst);
137 :
138 14 : d->len = cur;
139 14 : if (cur > 1)
140 8 : qsort(d->syn, d->len, sizeof(Syn), compare_syn);
141 :
142 14 : pfree(real_filename);
143 14 : }
144 :
145 : Datum
146 15 : dxsyn_init(PG_FUNCTION_ARGS)
147 : {
148 15 : List *dictoptions = (List *) PG_GETARG_POINTER(0);
149 : DictSyn *d;
150 : ListCell *l;
151 15 : char *filename = NULL;
152 :
153 15 : d = palloc0_object(DictSyn);
154 15 : d->len = 0;
155 15 : d->syn = NULL;
156 15 : d->matchorig = true;
157 15 : d->keeporig = true;
158 15 : d->matchsynonyms = false;
159 15 : d->keepsynonyms = true;
160 :
161 85 : foreach(l, dictoptions)
162 : {
163 70 : DefElem *defel = (DefElem *) lfirst(l);
164 :
165 70 : if (strcmp(defel->defname, "matchorig") == 0)
166 : {
167 14 : d->matchorig = defGetBoolean(defel);
168 : }
169 56 : else if (strcmp(defel->defname, "keeporig") == 0)
170 : {
171 14 : d->keeporig = defGetBoolean(defel);
172 : }
173 42 : else if (strcmp(defel->defname, "matchsynonyms") == 0)
174 : {
175 14 : d->matchsynonyms = defGetBoolean(defel);
176 : }
177 28 : else if (strcmp(defel->defname, "keepsynonyms") == 0)
178 : {
179 14 : d->keepsynonyms = defGetBoolean(defel);
180 : }
181 14 : else if (strcmp(defel->defname, "rules") == 0)
182 : {
183 : /* we can't read the rules before parsing all options! */
184 14 : 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 15 : if (filename)
196 14 : read_dictionary(d, filename);
197 :
198 15 : PG_RETURN_POINTER(d);
199 : }
200 :
201 : Datum
202 21 : dxsyn_lexize(PG_FUNCTION_ARGS)
203 : {
204 21 : DictSyn *d = (DictSyn *) PG_GETARG_POINTER(0);
205 21 : char *in = (char *) PG_GETARG_POINTER(1);
206 21 : int length = PG_GETARG_INT32(2);
207 : Syn word;
208 : Syn *found;
209 21 : TSLexeme *res = NULL;
210 :
211 21 : if (!length || d->len == 0)
212 3 : PG_RETURN_POINTER(NULL);
213 :
214 : /* Create search pattern */
215 : {
216 18 : char *temp = pnstrdup(in, length);
217 :
218 18 : word.key = str_tolower(temp, length, DEFAULT_COLLATION_OID);
219 18 : pfree(temp);
220 18 : word.value = NULL;
221 : }
222 :
223 : /* Look for matching syn */
224 18 : found = (Syn *) bsearch(&word, d->syn, d->len, sizeof(Syn), compare_syn);
225 18 : pfree(word.key);
226 :
227 18 : if (!found)
228 9 : PG_RETURN_POINTER(NULL);
229 :
230 : /* Parse string of synonyms and return array of words */
231 : {
232 9 : char *value = found->value;
233 : char *syn;
234 : char *pos;
235 : char *end;
236 9 : int nsyns = 0;
237 :
238 9 : res = palloc_object(TSLexeme);
239 :
240 9 : pos = value;
241 33 : while ((syn = find_word(pos, &end)) != NULL)
242 : {
243 27 : res = repalloc(res, sizeof(TSLexeme) * (nsyns + 2));
244 :
245 : /* The first word is output only if keeporig=true */
246 27 : if (pos != value || d->keeporig)
247 : {
248 22 : res[nsyns].lexeme = pnstrdup(syn, end - syn);
249 22 : res[nsyns].nvariant = 0;
250 22 : res[nsyns].flags = 0;
251 22 : nsyns++;
252 : }
253 :
254 27 : pos = end;
255 :
256 : /* Stop if we are not to output the synonyms */
257 27 : if (!d->keepsynonyms)
258 3 : break;
259 : }
260 9 : res[nsyns].lexeme = NULL;
261 : }
262 :
263 9 : PG_RETURN_POINTER(res);
264 : }
|