Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * dict_synonym.c
4 : * Synonym dictionary: replace word by its synonym
5 : *
6 : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7 : *
8 : *
9 : * IDENTIFICATION
10 : * src/backend/tsearch/dict_synonym.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 : #include "postgres.h"
15 :
16 : #include "commands/defrem.h"
17 : #include "tsearch/ts_locale.h"
18 : #include "tsearch/ts_public.h"
19 : #include "utils/fmgrprotos.h"
20 :
21 : typedef struct
22 : {
23 : char *in;
24 : char *out;
25 : int outlen;
26 : uint16 flags;
27 : } Syn;
28 :
29 : typedef struct
30 : {
31 : int len; /* length of syn array */
32 : Syn *syn;
33 : bool case_sensitive;
34 : } DictSyn;
35 :
36 : /*
37 : * Finds the next whitespace-delimited word within the 'in' string.
38 : * Returns a pointer to the first character of the word, and a pointer
39 : * to the next byte after the last character in the word (in *end).
40 : * Character '*' at the end of word will not be treated as word
41 : * character if flags is not null.
42 : */
43 : static char *
44 440 : findwrd(char *in, char **end, uint16 *flags)
45 : {
46 : char *start;
47 : char *lastchar;
48 :
49 : /* Skip leading spaces */
50 440 : while (*in && t_isspace(in))
51 0 : in += pg_mblen(in);
52 :
53 : /* Return NULL on empty lines */
54 440 : if (*in == '\0')
55 : {
56 0 : *end = NULL;
57 0 : return NULL;
58 : }
59 :
60 440 : lastchar = start = in;
61 :
62 : /* Find end of word */
63 3212 : while (*in && !t_isspace(in))
64 : {
65 2772 : lastchar = in;
66 2772 : in += pg_mblen(in);
67 : }
68 :
69 440 : if (in - lastchar == 1 && t_iseq(lastchar, '*') && flags)
70 : {
71 44 : *flags = TSL_PREFIX;
72 44 : *end = lastchar;
73 : }
74 : else
75 : {
76 396 : if (flags)
77 176 : *flags = 0;
78 396 : *end = in;
79 : }
80 :
81 440 : return start;
82 : }
83 :
84 : static int
85 1304 : compareSyn(const void *a, const void *b)
86 : {
87 1304 : return strcmp(((const Syn *) a)->in, ((const Syn *) b)->in);
88 : }
89 :
90 :
91 : Datum
92 50 : dsynonym_init(PG_FUNCTION_ARGS)
93 : {
94 50 : List *dictoptions = (List *) PG_GETARG_POINTER(0);
95 : DictSyn *d;
96 : ListCell *l;
97 50 : char *filename = NULL;
98 50 : bool case_sensitive = false;
99 : tsearch_readline_state trst;
100 : char *starti,
101 : *starto,
102 50 : *end = NULL;
103 50 : int cur = 0;
104 50 : char *line = NULL;
105 50 : uint16 flags = 0;
106 :
107 132 : foreach(l, dictoptions)
108 : {
109 88 : DefElem *defel = (DefElem *) lfirst(l);
110 :
111 88 : if (strcmp(defel->defname, "synonyms") == 0)
112 50 : filename = defGetString(defel);
113 38 : else if (strcmp(defel->defname, "casesensitive") == 0)
114 38 : case_sensitive = defGetBoolean(defel);
115 : else
116 0 : ereport(ERROR,
117 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
118 : errmsg("unrecognized synonym parameter: \"%s\"",
119 : defel->defname)));
120 : }
121 :
122 44 : if (!filename)
123 0 : ereport(ERROR,
124 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
125 : errmsg("missing Synonyms parameter")));
126 :
127 44 : filename = get_tsearch_config_filename(filename, "syn");
128 :
129 44 : if (!tsearch_readline_begin(&trst, filename))
130 0 : ereport(ERROR,
131 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
132 : errmsg("could not open synonym file \"%s\": %m",
133 : filename)));
134 :
135 44 : d = (DictSyn *) palloc0(sizeof(DictSyn));
136 :
137 264 : while ((line = tsearch_readline(&trst)) != NULL)
138 : {
139 220 : starti = findwrd(line, &end, NULL);
140 220 : if (!starti)
141 : {
142 : /* Empty line */
143 0 : goto skipline;
144 : }
145 220 : if (*end == '\0')
146 : {
147 : /* A line with only one word. Ignore silently. */
148 0 : goto skipline;
149 : }
150 220 : *end = '\0';
151 :
152 220 : starto = findwrd(end + 1, &end, &flags);
153 220 : if (!starto)
154 : {
155 : /* A line with only one word (+whitespace). Ignore silently. */
156 0 : goto skipline;
157 : }
158 220 : *end = '\0';
159 :
160 : /*
161 : * starti now points to the first word, and starto to the second word
162 : * on the line, with a \0 terminator at the end of both words.
163 : */
164 :
165 220 : if (cur >= d->len)
166 : {
167 44 : if (d->len == 0)
168 : {
169 44 : d->len = 64;
170 44 : d->syn = (Syn *) palloc(sizeof(Syn) * d->len);
171 : }
172 : else
173 : {
174 0 : d->len *= 2;
175 0 : d->syn = (Syn *) repalloc(d->syn, sizeof(Syn) * d->len);
176 : }
177 : }
178 :
179 220 : if (case_sensitive)
180 : {
181 60 : d->syn[cur].in = pstrdup(starti);
182 60 : d->syn[cur].out = pstrdup(starto);
183 : }
184 : else
185 : {
186 160 : d->syn[cur].in = lowerstr(starti);
187 160 : d->syn[cur].out = lowerstr(starto);
188 : }
189 :
190 220 : d->syn[cur].outlen = strlen(starto);
191 220 : d->syn[cur].flags = flags;
192 :
193 220 : cur++;
194 :
195 220 : skipline:
196 220 : pfree(line);
197 : }
198 :
199 44 : tsearch_readline_end(&trst);
200 :
201 44 : d->len = cur;
202 44 : qsort(d->syn, d->len, sizeof(Syn), compareSyn);
203 :
204 44 : d->case_sensitive = case_sensitive;
205 :
206 44 : PG_RETURN_POINTER(d);
207 : }
208 :
209 : Datum
210 366 : dsynonym_lexize(PG_FUNCTION_ARGS)
211 : {
212 366 : DictSyn *d = (DictSyn *) PG_GETARG_POINTER(0);
213 366 : char *in = (char *) PG_GETARG_POINTER(1);
214 366 : int32 len = PG_GETARG_INT32(2);
215 : Syn key,
216 : *found;
217 : TSLexeme *res;
218 :
219 : /* note: d->len test protects against Solaris bsearch-of-no-items bug */
220 366 : if (len <= 0 || d->len <= 0)
221 0 : PG_RETURN_POINTER(NULL);
222 :
223 366 : if (d->case_sensitive)
224 6 : key.in = pnstrdup(in, len);
225 : else
226 360 : key.in = lowerstr_with_len(in, len);
227 :
228 366 : key.out = NULL;
229 :
230 366 : found = (Syn *) bsearch(&key, d->syn, d->len, sizeof(Syn), compareSyn);
231 366 : pfree(key.in);
232 :
233 366 : if (!found)
234 300 : PG_RETURN_POINTER(NULL);
235 :
236 66 : res = palloc0(sizeof(TSLexeme) * 2);
237 66 : res[0].lexeme = pnstrdup(found->out, found->outlen);
238 66 : res[0].flags = found->flags;
239 :
240 66 : PG_RETURN_POINTER(res);
241 : }
|