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