1 /*
2  * contrib/citext/citext.c
3  */
4 #include "postgres.h"
5 
6 #include "access/hash.h"
7 #include "catalog/pg_collation.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 /*
157  *		==================
158  *		OPERATOR FUNCTIONS
159  *		==================
160  */
161 
162 PG_FUNCTION_INFO_V1(citext_eq);
163 
164 Datum
citext_eq(PG_FUNCTION_ARGS)165 citext_eq(PG_FUNCTION_ARGS)
166 {
167 	text	   *left = PG_GETARG_TEXT_PP(0);
168 	text	   *right = PG_GETARG_TEXT_PP(1);
169 	char	   *lcstr,
170 			   *rcstr;
171 	bool		result;
172 
173 	/* We can't compare lengths in advance of downcasing ... */
174 
175 	lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
176 	rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);
177 
178 	/*
179 	 * Since we only care about equality or not-equality, we can avoid all the
180 	 * expense of strcoll() here, and just do bitwise comparison.
181 	 */
182 	result = (strcmp(lcstr, rcstr) == 0);
183 
184 	pfree(lcstr);
185 	pfree(rcstr);
186 	PG_FREE_IF_COPY(left, 0);
187 	PG_FREE_IF_COPY(right, 1);
188 
189 	PG_RETURN_BOOL(result);
190 }
191 
192 PG_FUNCTION_INFO_V1(citext_ne);
193 
194 Datum
citext_ne(PG_FUNCTION_ARGS)195 citext_ne(PG_FUNCTION_ARGS)
196 {
197 	text	   *left = PG_GETARG_TEXT_PP(0);
198 	text	   *right = PG_GETARG_TEXT_PP(1);
199 	char	   *lcstr,
200 			   *rcstr;
201 	bool		result;
202 
203 	/* We can't compare lengths in advance of downcasing ... */
204 
205 	lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
206 	rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);
207 
208 	/*
209 	 * Since we only care about equality or not-equality, we can avoid all the
210 	 * expense of strcoll() here, and just do bitwise comparison.
211 	 */
212 	result = (strcmp(lcstr, rcstr) != 0);
213 
214 	pfree(lcstr);
215 	pfree(rcstr);
216 	PG_FREE_IF_COPY(left, 0);
217 	PG_FREE_IF_COPY(right, 1);
218 
219 	PG_RETURN_BOOL(result);
220 }
221 
222 PG_FUNCTION_INFO_V1(citext_lt);
223 
224 Datum
citext_lt(PG_FUNCTION_ARGS)225 citext_lt(PG_FUNCTION_ARGS)
226 {
227 	text	   *left = PG_GETARG_TEXT_PP(0);
228 	text	   *right = PG_GETARG_TEXT_PP(1);
229 	bool		result;
230 
231 	result = citextcmp(left, right, PG_GET_COLLATION()) < 0;
232 
233 	PG_FREE_IF_COPY(left, 0);
234 	PG_FREE_IF_COPY(right, 1);
235 
236 	PG_RETURN_BOOL(result);
237 }
238 
239 PG_FUNCTION_INFO_V1(citext_le);
240 
241 Datum
citext_le(PG_FUNCTION_ARGS)242 citext_le(PG_FUNCTION_ARGS)
243 {
244 	text	   *left = PG_GETARG_TEXT_PP(0);
245 	text	   *right = PG_GETARG_TEXT_PP(1);
246 	bool		result;
247 
248 	result = citextcmp(left, right, PG_GET_COLLATION()) <= 0;
249 
250 	PG_FREE_IF_COPY(left, 0);
251 	PG_FREE_IF_COPY(right, 1);
252 
253 	PG_RETURN_BOOL(result);
254 }
255 
256 PG_FUNCTION_INFO_V1(citext_gt);
257 
258 Datum
citext_gt(PG_FUNCTION_ARGS)259 citext_gt(PG_FUNCTION_ARGS)
260 {
261 	text	   *left = PG_GETARG_TEXT_PP(0);
262 	text	   *right = PG_GETARG_TEXT_PP(1);
263 	bool		result;
264 
265 	result = citextcmp(left, right, PG_GET_COLLATION()) > 0;
266 
267 	PG_FREE_IF_COPY(left, 0);
268 	PG_FREE_IF_COPY(right, 1);
269 
270 	PG_RETURN_BOOL(result);
271 }
272 
273 PG_FUNCTION_INFO_V1(citext_ge);
274 
275 Datum
citext_ge(PG_FUNCTION_ARGS)276 citext_ge(PG_FUNCTION_ARGS)
277 {
278 	text	   *left = PG_GETARG_TEXT_PP(0);
279 	text	   *right = PG_GETARG_TEXT_PP(1);
280 	bool		result;
281 
282 	result = citextcmp(left, right, PG_GET_COLLATION()) >= 0;
283 
284 	PG_FREE_IF_COPY(left, 0);
285 	PG_FREE_IF_COPY(right, 1);
286 
287 	PG_RETURN_BOOL(result);
288 }
289 
290 PG_FUNCTION_INFO_V1(citext_pattern_lt);
291 
292 Datum
citext_pattern_lt(PG_FUNCTION_ARGS)293 citext_pattern_lt(PG_FUNCTION_ARGS)
294 {
295 	text	   *left = PG_GETARG_TEXT_PP(0);
296 	text	   *right = PG_GETARG_TEXT_PP(1);
297 	bool		result;
298 
299 	result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) < 0;
300 
301 	PG_FREE_IF_COPY(left, 0);
302 	PG_FREE_IF_COPY(right, 1);
303 
304 	PG_RETURN_BOOL(result);
305 }
306 
307 PG_FUNCTION_INFO_V1(citext_pattern_le);
308 
309 Datum
citext_pattern_le(PG_FUNCTION_ARGS)310 citext_pattern_le(PG_FUNCTION_ARGS)
311 {
312 	text	   *left = PG_GETARG_TEXT_PP(0);
313 	text	   *right = PG_GETARG_TEXT_PP(1);
314 	bool		result;
315 
316 	result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) <= 0;
317 
318 	PG_FREE_IF_COPY(left, 0);
319 	PG_FREE_IF_COPY(right, 1);
320 
321 	PG_RETURN_BOOL(result);
322 }
323 
324 PG_FUNCTION_INFO_V1(citext_pattern_gt);
325 
326 Datum
citext_pattern_gt(PG_FUNCTION_ARGS)327 citext_pattern_gt(PG_FUNCTION_ARGS)
328 {
329 	text	   *left = PG_GETARG_TEXT_PP(0);
330 	text	   *right = PG_GETARG_TEXT_PP(1);
331 	bool		result;
332 
333 	result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) > 0;
334 
335 	PG_FREE_IF_COPY(left, 0);
336 	PG_FREE_IF_COPY(right, 1);
337 
338 	PG_RETURN_BOOL(result);
339 }
340 
341 PG_FUNCTION_INFO_V1(citext_pattern_ge);
342 
343 Datum
citext_pattern_ge(PG_FUNCTION_ARGS)344 citext_pattern_ge(PG_FUNCTION_ARGS)
345 {
346 	text	   *left = PG_GETARG_TEXT_PP(0);
347 	text	   *right = PG_GETARG_TEXT_PP(1);
348 	bool		result;
349 
350 	result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) >= 0;
351 
352 	PG_FREE_IF_COPY(left, 0);
353 	PG_FREE_IF_COPY(right, 1);
354 
355 	PG_RETURN_BOOL(result);
356 }
357 
358 /*
359  *		===================
360  *		AGGREGATE FUNCTIONS
361  *		===================
362  */
363 
364 PG_FUNCTION_INFO_V1(citext_smaller);
365 
366 Datum
citext_smaller(PG_FUNCTION_ARGS)367 citext_smaller(PG_FUNCTION_ARGS)
368 {
369 	text	   *left = PG_GETARG_TEXT_PP(0);
370 	text	   *right = PG_GETARG_TEXT_PP(1);
371 	text	   *result;
372 
373 	result = citextcmp(left, right, PG_GET_COLLATION()) < 0 ? left : right;
374 	PG_RETURN_TEXT_P(result);
375 }
376 
377 PG_FUNCTION_INFO_V1(citext_larger);
378 
379 Datum
citext_larger(PG_FUNCTION_ARGS)380 citext_larger(PG_FUNCTION_ARGS)
381 {
382 	text	   *left = PG_GETARG_TEXT_PP(0);
383 	text	   *right = PG_GETARG_TEXT_PP(1);
384 	text	   *result;
385 
386 	result = citextcmp(left, right, PG_GET_COLLATION()) > 0 ? left : right;
387 	PG_RETURN_TEXT_P(result);
388 }
389