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