Branch data 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 : : }
|