1 /*
2  * contrib/citext/citext.c
3  */
4 #include "postgres.h"
5 
6 #include "catalog/pg_collation.h"
7 #include "common/hashfn.h"
8 #include "utils/builtins.h"
9 #include "utils/formatting.h"
10 #include "utils/varlena.h"
11 
12 PG_MODULE_MAGIC;
13 
14 /*
15  *		====================
16  *		FORWARD DECLARATIONS
17  *		====================
18  */
19 
20 static int32 citextcmp(text *left, text *right, Oid collid);
21 static int32 internal_citext_pattern_cmp(text *left, text *right, Oid collid);
22 
23 /*
24  *		=================
25  *		UTILITY FUNCTIONS
26  *		=================
27  */
28 
29 /*
30  * citextcmp()
31  * Internal comparison function for citext strings.
32  * Returns int32 negative, zero, or positive.
33  */
34 static int32
citextcmp(text * left,text * right,Oid collid)35 citextcmp(text *left, text *right, Oid collid)
36 {
37 	char	   *lcstr,
38 			   *rcstr;
39 	int32		result;
40 
41 	/*
42 	 * We must do our str_tolower calls with DEFAULT_COLLATION_OID, not the
43 	 * input collation as you might expect.  This is so that the behavior of
44 	 * citext's equality and hashing functions is not collation-dependent.  We
45 	 * should change this once the core infrastructure is able to cope with
46 	 * collation-dependent equality and hashing functions.
47 	 */
48 
49 	lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
50 	rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);
51 
52 	result = varstr_cmp(lcstr, strlen(lcstr),
53 						rcstr, strlen(rcstr),
54 						collid);
55 
56 	pfree(lcstr);
57 	pfree(rcstr);
58 
59 	return result;
60 }
61 
62 /*
63  * citext_pattern_cmp()
64  * Internal character-by-character comparison function for citext strings.
65  * Returns int32 negative, zero, or positive.
66  */
67 static int32
internal_citext_pattern_cmp(text * left,text * right,Oid collid)68 internal_citext_pattern_cmp(text *left, text *right, Oid collid)
69 {
70 	char	   *lcstr,
71 			   *rcstr;
72 	int			llen,
73 				rlen;
74 	int32		result;
75 
76 	lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
77 	rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);
78 
79 	llen = strlen(lcstr);
80 	rlen = strlen(rcstr);
81 
82 	result = memcmp((void *) lcstr, (void *) rcstr, Min(llen, rlen));
83 	if (result == 0)
84 	{
85 		if (llen < rlen)
86 			result = -1;
87 		else if (llen > rlen)
88 			result = 1;
89 	}
90 
91 	pfree(lcstr);
92 	pfree(rcstr);
93 
94 	return result;
95 }
96 
97 /*
98  *		==================
99  *		INDEXING FUNCTIONS
100  *		==================
101  */
102 
103 PG_FUNCTION_INFO_V1(citext_cmp);
104 
105 Datum
citext_cmp(PG_FUNCTION_ARGS)106 citext_cmp(PG_FUNCTION_ARGS)
107 {
108 	text	   *left = PG_GETARG_TEXT_PP(0);
109 	text	   *right = PG_GETARG_TEXT_PP(1);
110 	int32		result;
111 
112 	result = citextcmp(left, right, PG_GET_COLLATION());
113 
114 	PG_FREE_IF_COPY(left, 0);
115 	PG_FREE_IF_COPY(right, 1);
116 
117 	PG_RETURN_INT32(result);
118 }
119 
120 PG_FUNCTION_INFO_V1(citext_pattern_cmp);
121 
122 Datum
citext_pattern_cmp(PG_FUNCTION_ARGS)123 citext_pattern_cmp(PG_FUNCTION_ARGS)
124 {
125 	text	   *left = PG_GETARG_TEXT_PP(0);
126 	text	   *right = PG_GETARG_TEXT_PP(1);
127 	int32		result;
128 
129 	result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION());
130 
131 	PG_FREE_IF_COPY(left, 0);
132 	PG_FREE_IF_COPY(right, 1);
133 
134 	PG_RETURN_INT32(result);
135 }
136 
137 PG_FUNCTION_INFO_V1(citext_hash);
138 
139 Datum
citext_hash(PG_FUNCTION_ARGS)140 citext_hash(PG_FUNCTION_ARGS)
141 {
142 	text	   *txt = PG_GETARG_TEXT_PP(0);
143 	char	   *str;
144 	Datum		result;
145 
146 	str = str_tolower(VARDATA_ANY(txt), VARSIZE_ANY_EXHDR(txt), DEFAULT_COLLATION_OID);
147 	result = hash_any((unsigned char *) str, strlen(str));
148 	pfree(str);
149 
150 	/* Avoid leaking memory for toasted inputs */
151 	PG_FREE_IF_COPY(txt, 0);
152 
153 	PG_RETURN_DATUM(result);
154 }
155 
156 PG_FUNCTION_INFO_V1(citext_hash_extended);
157 
158 Datum
citext_hash_extended(PG_FUNCTION_ARGS)159 citext_hash_extended(PG_FUNCTION_ARGS)
160 {
161 	text	   *txt = PG_GETARG_TEXT_PP(0);
162 	uint64		seed = PG_GETARG_INT64(1);
163 	char	   *str;
164 	Datum		result;
165 
166 	str = str_tolower(VARDATA_ANY(txt), VARSIZE_ANY_EXHDR(txt), DEFAULT_COLLATION_OID);
167 	result = hash_any_extended((unsigned char *) str, strlen(str), seed);
168 	pfree(str);
169 
170 	/* Avoid leaking memory for toasted inputs */
171 	PG_FREE_IF_COPY(txt, 0);
172 
173 	PG_RETURN_DATUM(result);
174 }
175 
176 /*
177  *		==================
178  *		OPERATOR FUNCTIONS
179  *		==================
180  */
181 
182 PG_FUNCTION_INFO_V1(citext_eq);
183 
184 Datum
citext_eq(PG_FUNCTION_ARGS)185 citext_eq(PG_FUNCTION_ARGS)
186 {
187 	text	   *left = PG_GETARG_TEXT_PP(0);
188 	text	   *right = PG_GETARG_TEXT_PP(1);
189 	char	   *lcstr,
190 			   *rcstr;
191 	bool		result;
192 
193 	/* We can't compare lengths in advance of downcasing ... */
194 
195 	lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
196 	rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);
197 
198 	/*
199 	 * Since we only care about equality or not-equality, we can avoid all the
200 	 * expense of strcoll() here, and just do bitwise comparison.
201 	 */
202 	result = (strcmp(lcstr, rcstr) == 0);
203 
204 	pfree(lcstr);
205 	pfree(rcstr);
206 	PG_FREE_IF_COPY(left, 0);
207 	PG_FREE_IF_COPY(right, 1);
208 
209 	PG_RETURN_BOOL(result);
210 }
211 
212 PG_FUNCTION_INFO_V1(citext_ne);
213 
214 Datum
citext_ne(PG_FUNCTION_ARGS)215 citext_ne(PG_FUNCTION_ARGS)
216 {
217 	text	   *left = PG_GETARG_TEXT_PP(0);
218 	text	   *right = PG_GETARG_TEXT_PP(1);
219 	char	   *lcstr,
220 			   *rcstr;
221 	bool		result;
222 
223 	/* We can't compare lengths in advance of downcasing ... */
224 
225 	lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
226 	rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);
227 
228 	/*
229 	 * Since we only care about equality or not-equality, we can avoid all the
230 	 * expense of strcoll() here, and just do bitwise comparison.
231 	 */
232 	result = (strcmp(lcstr, rcstr) != 0);
233 
234 	pfree(lcstr);
235 	pfree(rcstr);
236 	PG_FREE_IF_COPY(left, 0);
237 	PG_FREE_IF_COPY(right, 1);
238 
239 	PG_RETURN_BOOL(result);
240 }
241 
242 PG_FUNCTION_INFO_V1(citext_lt);
243 
244 Datum
citext_lt(PG_FUNCTION_ARGS)245 citext_lt(PG_FUNCTION_ARGS)
246 {
247 	text	   *left = PG_GETARG_TEXT_PP(0);
248 	text	   *right = PG_GETARG_TEXT_PP(1);
249 	bool		result;
250 
251 	result = citextcmp(left, right, PG_GET_COLLATION()) < 0;
252 
253 	PG_FREE_IF_COPY(left, 0);
254 	PG_FREE_IF_COPY(right, 1);
255 
256 	PG_RETURN_BOOL(result);
257 }
258 
259 PG_FUNCTION_INFO_V1(citext_le);
260 
261 Datum
citext_le(PG_FUNCTION_ARGS)262 citext_le(PG_FUNCTION_ARGS)
263 {
264 	text	   *left = PG_GETARG_TEXT_PP(0);
265 	text	   *right = PG_GETARG_TEXT_PP(1);
266 	bool		result;
267 
268 	result = citextcmp(left, right, PG_GET_COLLATION()) <= 0;
269 
270 	PG_FREE_IF_COPY(left, 0);
271 	PG_FREE_IF_COPY(right, 1);
272 
273 	PG_RETURN_BOOL(result);
274 }
275 
276 PG_FUNCTION_INFO_V1(citext_gt);
277 
278 Datum
citext_gt(PG_FUNCTION_ARGS)279 citext_gt(PG_FUNCTION_ARGS)
280 {
281 	text	   *left = PG_GETARG_TEXT_PP(0);
282 	text	   *right = PG_GETARG_TEXT_PP(1);
283 	bool		result;
284 
285 	result = citextcmp(left, right, PG_GET_COLLATION()) > 0;
286 
287 	PG_FREE_IF_COPY(left, 0);
288 	PG_FREE_IF_COPY(right, 1);
289 
290 	PG_RETURN_BOOL(result);
291 }
292 
293 PG_FUNCTION_INFO_V1(citext_ge);
294 
295 Datum
citext_ge(PG_FUNCTION_ARGS)296 citext_ge(PG_FUNCTION_ARGS)
297 {
298 	text	   *left = PG_GETARG_TEXT_PP(0);
299 	text	   *right = PG_GETARG_TEXT_PP(1);
300 	bool		result;
301 
302 	result = citextcmp(left, right, PG_GET_COLLATION()) >= 0;
303 
304 	PG_FREE_IF_COPY(left, 0);
305 	PG_FREE_IF_COPY(right, 1);
306 
307 	PG_RETURN_BOOL(result);
308 }
309 
310 PG_FUNCTION_INFO_V1(citext_pattern_lt);
311 
312 Datum
citext_pattern_lt(PG_FUNCTION_ARGS)313 citext_pattern_lt(PG_FUNCTION_ARGS)
314 {
315 	text	   *left = PG_GETARG_TEXT_PP(0);
316 	text	   *right = PG_GETARG_TEXT_PP(1);
317 	bool		result;
318 
319 	result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) < 0;
320 
321 	PG_FREE_IF_COPY(left, 0);
322 	PG_FREE_IF_COPY(right, 1);
323 
324 	PG_RETURN_BOOL(result);
325 }
326 
327 PG_FUNCTION_INFO_V1(citext_pattern_le);
328 
329 Datum
citext_pattern_le(PG_FUNCTION_ARGS)330 citext_pattern_le(PG_FUNCTION_ARGS)
331 {
332 	text	   *left = PG_GETARG_TEXT_PP(0);
333 	text	   *right = PG_GETARG_TEXT_PP(1);
334 	bool		result;
335 
336 	result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) <= 0;
337 
338 	PG_FREE_IF_COPY(left, 0);
339 	PG_FREE_IF_COPY(right, 1);
340 
341 	PG_RETURN_BOOL(result);
342 }
343 
344 PG_FUNCTION_INFO_V1(citext_pattern_gt);
345 
346 Datum
citext_pattern_gt(PG_FUNCTION_ARGS)347 citext_pattern_gt(PG_FUNCTION_ARGS)
348 {
349 	text	   *left = PG_GETARG_TEXT_PP(0);
350 	text	   *right = PG_GETARG_TEXT_PP(1);
351 	bool		result;
352 
353 	result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) > 0;
354 
355 	PG_FREE_IF_COPY(left, 0);
356 	PG_FREE_IF_COPY(right, 1);
357 
358 	PG_RETURN_BOOL(result);
359 }
360 
361 PG_FUNCTION_INFO_V1(citext_pattern_ge);
362 
363 Datum
citext_pattern_ge(PG_FUNCTION_ARGS)364 citext_pattern_ge(PG_FUNCTION_ARGS)
365 {
366 	text	   *left = PG_GETARG_TEXT_PP(0);
367 	text	   *right = PG_GETARG_TEXT_PP(1);
368 	bool		result;
369 
370 	result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) >= 0;
371 
372 	PG_FREE_IF_COPY(left, 0);
373 	PG_FREE_IF_COPY(right, 1);
374 
375 	PG_RETURN_BOOL(result);
376 }
377 
378 /*
379  *		===================
380  *		AGGREGATE FUNCTIONS
381  *		===================
382  */
383 
384 PG_FUNCTION_INFO_V1(citext_smaller);
385 
386 Datum
citext_smaller(PG_FUNCTION_ARGS)387 citext_smaller(PG_FUNCTION_ARGS)
388 {
389 	text	   *left = PG_GETARG_TEXT_PP(0);
390 	text	   *right = PG_GETARG_TEXT_PP(1);
391 	text	   *result;
392 
393 	result = citextcmp(left, right, PG_GET_COLLATION()) < 0 ? left : right;
394 	PG_RETURN_TEXT_P(result);
395 }
396 
397 PG_FUNCTION_INFO_V1(citext_larger);
398 
399 Datum
citext_larger(PG_FUNCTION_ARGS)400 citext_larger(PG_FUNCTION_ARGS)
401 {
402 	text	   *left = PG_GETARG_TEXT_PP(0);
403 	text	   *right = PG_GETARG_TEXT_PP(1);
404 	text	   *result;
405 
406 	result = citextcmp(left, right, PG_GET_COLLATION()) > 0 ? left : right;
407 	PG_RETURN_TEXT_P(result);
408 }
409