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 : 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 220 : findwrd(char *in, char **end, uint16 *flags)
47 : {
48 : char *start;
49 : char *lastchar;
50 :
51 : /* Skip leading spaces */
52 220 : while (*in && isspace((unsigned char) *in))
53 0 : in += pg_mblen_cstr(in);
54 :
55 : /* Return NULL on empty lines */
56 220 : if (*in == '\0')
57 : {
58 0 : *end = NULL;
59 0 : return NULL;
60 : }
61 :
62 220 : lastchar = start = in;
63 :
64 : /* Find end of word */
65 1606 : while (*in && !isspace((unsigned char) *in))
66 : {
67 1386 : lastchar = in;
68 1386 : in += pg_mblen_cstr(in);
69 : }
70 :
71 220 : if (in - lastchar == 1 && t_iseq(lastchar, '*') && flags)
72 : {
73 22 : *flags = TSL_PREFIX;
74 22 : *end = lastchar;
75 : }
76 : else
77 : {
78 198 : if (flags)
79 88 : *flags = 0;
80 198 : *end = in;
81 : }
82 :
83 220 : return start;
84 : }
85 :
86 : static int
87 652 : compareSyn(const void *a, const void *b)
88 : {
89 652 : return strcmp(((const Syn *) a)->in, ((const Syn *) b)->in);
90 : }
91 :
92 :
93 : Datum
94 25 : dsynonym_init(PG_FUNCTION_ARGS)
95 : {
96 25 : List *dictoptions = (List *) PG_GETARG_POINTER(0);
97 : DictSyn *d;
98 : ListCell *l;
99 25 : char *filename = NULL;
100 25 : bool case_sensitive = false;
101 : tsearch_readline_state trst;
102 : char *starti,
103 : *starto,
104 25 : *end = NULL;
105 25 : int cur = 0;
106 25 : char *line = NULL;
107 25 : uint16 flags = 0;
108 :
109 66 : foreach(l, dictoptions)
110 : {
111 44 : DefElem *defel = (DefElem *) lfirst(l);
112 :
113 44 : if (strcmp(defel->defname, "synonyms") == 0)
114 25 : filename = defGetString(defel);
115 19 : else if (strcmp(defel->defname, "casesensitive") == 0)
116 19 : 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 22 : if (!filename)
125 0 : ereport(ERROR,
126 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
127 : errmsg("missing Synonyms parameter")));
128 :
129 22 : filename = get_tsearch_config_filename(filename, "syn");
130 :
131 22 : 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 22 : d = palloc0_object(DictSyn);
138 :
139 132 : while ((line = tsearch_readline(&trst)) != NULL)
140 : {
141 110 : starti = findwrd(line, &end, NULL);
142 110 : if (!starti)
143 : {
144 : /* Empty line */
145 0 : goto skipline;
146 : }
147 110 : if (*end == '\0')
148 : {
149 : /* A line with only one word. Ignore silently. */
150 0 : goto skipline;
151 : }
152 110 : *end = '\0';
153 :
154 110 : starto = findwrd(end + 1, &end, &flags);
155 110 : if (!starto)
156 : {
157 : /* A line with only one word (+whitespace). Ignore silently. */
158 0 : goto skipline;
159 : }
160 110 : *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 110 : if (cur >= d->len)
168 : {
169 22 : if (d->len == 0)
170 : {
171 22 : d->len = 64;
172 22 : d->syn = palloc_array(Syn, d->len);
173 : }
174 : else
175 : {
176 0 : d->len *= 2;
177 0 : d->syn = repalloc_array(d->syn, Syn, d->len);
178 : }
179 : }
180 :
181 110 : if (case_sensitive)
182 : {
183 30 : d->syn[cur].in = pstrdup(starti);
184 30 : d->syn[cur].out = pstrdup(starto);
185 : }
186 : else
187 : {
188 80 : d->syn[cur].in = str_tolower(starti, strlen(starti), DEFAULT_COLLATION_OID);
189 80 : d->syn[cur].out = str_tolower(starto, strlen(starto), DEFAULT_COLLATION_OID);
190 : }
191 :
192 110 : d->syn[cur].outlen = strlen(starto);
193 110 : d->syn[cur].flags = flags;
194 :
195 110 : cur++;
196 :
197 110 : skipline:
198 110 : pfree(line);
199 : }
200 :
201 22 : tsearch_readline_end(&trst);
202 22 : pfree(filename);
203 :
204 22 : d->len = cur;
205 22 : qsort(d->syn, d->len, sizeof(Syn), compareSyn);
206 :
207 22 : d->case_sensitive = case_sensitive;
208 :
209 22 : PG_RETURN_POINTER(d);
210 : }
211 :
212 : Datum
213 183 : dsynonym_lexize(PG_FUNCTION_ARGS)
214 : {
215 183 : DictSyn *d = (DictSyn *) PG_GETARG_POINTER(0);
216 183 : char *in = (char *) PG_GETARG_POINTER(1);
217 183 : int32 len = PG_GETARG_INT32(2);
218 : Syn key,
219 : *found;
220 : TSLexeme *res;
221 :
222 : /* note: d->len test protects against Solaris bsearch-of-no-items bug */
223 183 : if (len <= 0 || d->len <= 0)
224 0 : PG_RETURN_POINTER(NULL);
225 :
226 183 : if (d->case_sensitive)
227 3 : key.in = pnstrdup(in, len);
228 : else
229 180 : key.in = str_tolower(in, len, DEFAULT_COLLATION_OID);
230 :
231 183 : key.out = NULL;
232 :
233 183 : found = (Syn *) bsearch(&key, d->syn, d->len, sizeof(Syn), compareSyn);
234 183 : pfree(key.in);
235 :
236 183 : if (!found)
237 150 : PG_RETURN_POINTER(NULL);
238 :
239 33 : res = palloc0_array(TSLexeme, 2);
240 33 : res[0].lexeme = pnstrdup(found->out, found->outlen);
241 33 : res[0].flags = found->flags;
242 :
243 33 : PG_RETURN_POINTER(res);
244 : }
|