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