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