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