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