text: /*
   * $PostgreSQL$
   */
  #include "postgres.h"
  
  #include "access/hash.h"
  #include "fmgr.h"
  #include "utils/builtins.h"
  #include "utils/formatting.h"
  
  #ifdef PG_MODULE_MAGIC
  PG_MODULE_MAGIC;
  #endif
  
  /*
   *		====================
   *		FORWARD DECLARATIONS
   *		====================
   */
  
  static int32 citextcmp(text *left, text *right);
  extern Datum citext_cmp(PG_FUNCTION_ARGS);
  extern Datum citext_hash(PG_FUNCTION_ARGS);
  extern Datum citext_eq(PG_FUNCTION_ARGS);
  extern Datum citext_ne(PG_FUNCTION_ARGS);
  extern Datum citext_gt(PG_FUNCTION_ARGS);
  extern Datum citext_ge(PG_FUNCTION_ARGS);
  extern Datum citext_lt(PG_FUNCTION_ARGS);
  extern Datum citext_le(PG_FUNCTION_ARGS);
  extern Datum citext_smaller(PG_FUNCTION_ARGS);
  extern Datum citext_larger(PG_FUNCTION_ARGS);
  
  /*
   *		=================
   *		UTILITY FUNCTIONS
   *		=================
   */
  
  /*
   * citextcmp()
   * Internal comparison function for citext strings.
   * Returns int32 negative, zero, or positive.
   */
  static int32
  citextcmp(text *left, text *right)
  {
  	char	   *lcstr,
  			   *rcstr;
  	int32		result;
  
  	lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left));
  	rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right));
  
  	result = varstr_cmp(lcstr, strlen(lcstr),
  						rcstr, strlen(rcstr));
  
  	pfree(lcstr);
  	pfree(rcstr);
  
  	return result;
  }
  
  /*
   *		==================
   *		INDEXING FUNCTIONS
   *		==================
   */
  
  PG_FUNCTION_INFO_V1(citext_cmp);
  
  Datum
  citext_cmp(PG_FUNCTION_ARGS)
  {
  	text	   *left = PG_GETARG_TEXT_PP(0);
  	text	   *right = PG_GETARG_TEXT_PP(1);
  	int32		result;
  
  	result = citextcmp(left, right);
  
  	PG_FREE_IF_COPY(left, 0);
  	PG_FREE_IF_COPY(right, 1);
  
  	PG_RETURN_INT32(result);
  }
  
  PG_FUNCTION_INFO_V1(citext_hash);
  
  Datum
  citext_hash(PG_FUNCTION_ARGS)
  {
  	text	   *txt = PG_GETARG_TEXT_PP(0);
  	char	   *str;
  	Datum		result;
  
  	str = str_tolower(VARDATA_ANY(txt), VARSIZE_ANY_EXHDR(txt));
  	result = hash_any((unsigned char *) str, strlen(str));
  	pfree(str);
  
  	/* Avoid leaking memory for toasted inputs */
  	PG_FREE_IF_COPY(txt, 0);
  
  	PG_RETURN_DATUM(result);
  }
  
  /*
   *		==================
   *		OPERATOR FUNCTIONS
   *		==================
   */
  
  PG_FUNCTION_INFO_V1(citext_eq);
  
  Datum
  citext_eq(PG_FUNCTION_ARGS)
  {
  	text	   *left = PG_GETARG_TEXT_PP(0);
  	text	   *right = PG_GETARG_TEXT_PP(1);
  	char	   *lcstr,
  			   *rcstr;
  	bool		result;
  
  	/* We can't compare lengths in advance of downcasing ... */
  
  	lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left));
  	rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right));
  
  	/*
  	 * Since we only care about equality or not-equality, we can avoid all the
  	 * expense of strcoll() here, and just do bitwise comparison.
  	 */
  	result = (strcmp(lcstr, rcstr) == 0);
  
  	pfree(lcstr);
  	pfree(rcstr);
  	PG_FREE_IF_COPY(left, 0);
  	PG_FREE_IF_COPY(right, 1);
  
  	PG_RETURN_BOOL(result);
  }
  
  PG_FUNCTION_INFO_V1(citext_ne);
  
  Datum
  citext_ne(PG_FUNCTION_ARGS)
  {
  	text	   *left = PG_GETARG_TEXT_PP(0);
  	text	   *right = PG_GETARG_TEXT_PP(1);
  	char	   *lcstr,
  			   *rcstr;
  	bool		result;
  
  	/* We can't compare lengths in advance of downcasing ... */
  
  	lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left));
  	rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right));
  
  	/*
  	 * Since we only care about equality or not-equality, we can avoid all the
  	 * expense of strcoll() here, and just do bitwise comparison.
  	 */
  	result = (strcmp(lcstr, rcstr) != 0);
  
  	pfree(lcstr);
  	pfree(rcstr);
  	PG_FREE_IF_COPY(left, 0);
  	PG_FREE_IF_COPY(right, 1);
  
  	PG_RETURN_BOOL(result);
  }
  
  PG_FUNCTION_INFO_V1(citext_lt);
  
  Datum
  citext_lt(PG_FUNCTION_ARGS)
  {
  	text	   *left = PG_GETARG_TEXT_PP(0);
  	text	   *right = PG_GETARG_TEXT_PP(1);
  	bool		result;
  
  	result = citextcmp(left, right) < 0;
  
  	PG_FREE_IF_COPY(left, 0);
  	PG_FREE_IF_COPY(right, 1);
  
  	PG_RETURN_BOOL(result);
  }
  
  PG_FUNCTION_INFO_V1(citext_le);
  
  Datum
  citext_le(PG_FUNCTION_ARGS)
  {
  	text	   *left = PG_GETARG_TEXT_PP(0);
  	text	   *right = PG_GETARG_TEXT_PP(1);
  	bool		result;
  
  	result = citextcmp(left, right) <= 0;
  
  	PG_FREE_IF_COPY(left, 0);
  	PG_FREE_IF_COPY(right, 1);
  
  	PG_RETURN_BOOL(result);
  }
  
  PG_FUNCTION_INFO_V1(citext_gt);
  
  Datum
  citext_gt(PG_FUNCTION_ARGS)
  {
  	text	   *left = PG_GETARG_TEXT_PP(0);
  	text	   *right = PG_GETARG_TEXT_PP(1);
  	bool		result;
  
  	result = citextcmp(left, right) > 0;
  
  	PG_FREE_IF_COPY(left, 0);
  	PG_FREE_IF_COPY(right, 1);
  
  	PG_RETURN_BOOL(result);
  }
  
  PG_FUNCTION_INFO_V1(citext_ge);
  
  Datum
  citext_ge(PG_FUNCTION_ARGS)
  {
  	text	   *left = PG_GETARG_TEXT_PP(0);
  	text	   *right = PG_GETARG_TEXT_PP(1);
  	bool		result;
  
  	result = citextcmp(left, right) >= 0;
  
  	PG_FREE_IF_COPY(left, 0);
  	PG_FREE_IF_COPY(right, 1);
  
  	PG_RETURN_BOOL(result);
  }
  
  /*
   *		===================
   *		AGGREGATE FUNCTIONS
   *		===================
   */
  
  PG_FUNCTION_INFO_V1(citext_smaller);
  
  Datum
  citext_smaller(PG_FUNCTION_ARGS)
  {
  	text	   *left = PG_GETARG_TEXT_PP(0);
  	text	   *right = PG_GETARG_TEXT_PP(1);
  	text	   *result;
  
  	result = citextcmp(left, right) < 0 ? left : right;
  	PG_RETURN_TEXT_P(result);
  }
  
  PG_FUNCTION_INFO_V1(citext_larger);
  
  Datum
  citext_larger(PG_FUNCTION_ARGS)
  {
  	text	   *left = PG_GETARG_TEXT_PP(0);
  	text	   *right = PG_GETARG_TEXT_PP(1);
  	text	   *result;
  
  	result = citextcmp(left, right) > 0 ? left : right;
  	PG_RETURN_TEXT_P(result);
  }
  