2008-07-30 02:31:20 +08:00
|
|
|
/*
|
2010-09-21 04:08:53 +08:00
|
|
|
* contrib/citext/citext.c
|
2008-07-30 02:31:20 +08:00
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
2011-06-09 03:24:27 +08:00
|
|
|
#include "catalog/pg_collation.h"
|
2020-02-27 11:55:41 +08:00
|
|
|
#include "common/hashfn.h"
|
2008-07-30 02:31:20 +08:00
|
|
|
#include "utils/builtins.h"
|
|
|
|
#include "utils/formatting.h"
|
2017-01-21 09:29:53 +08:00
|
|
|
#include "utils/varlena.h"
|
2008-07-30 02:31:20 +08:00
|
|
|
|
|
|
|
PG_MODULE_MAGIC;
|
|
|
|
|
|
|
|
/*
|
2009-06-11 22:49:15 +08:00
|
|
|
* ====================
|
|
|
|
* FORWARD DECLARATIONS
|
|
|
|
* ====================
|
2008-07-30 02:31:20 +08:00
|
|
|
*/
|
|
|
|
|
2011-02-09 05:04:18 +08:00
|
|
|
static int32 citextcmp(text *left, text *right, Oid collid);
|
2017-09-19 20:31:45 +08:00
|
|
|
static int32 internal_citext_pattern_cmp(text *left, text *right, Oid collid);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
|
|
|
/*
|
2009-06-11 22:49:15 +08:00
|
|
|
* =================
|
|
|
|
* UTILITY FUNCTIONS
|
|
|
|
* =================
|
2008-07-30 02:31:20 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* citextcmp()
|
|
|
|
* Internal comparison function for citext strings.
|
|
|
|
* Returns int32 negative, zero, or positive.
|
|
|
|
*/
|
|
|
|
static int32
|
2011-02-09 05:04:18 +08:00
|
|
|
citextcmp(text *left, text *right, Oid collid)
|
2008-07-30 02:31:20 +08:00
|
|
|
{
|
2009-06-11 22:49:15 +08:00
|
|
|
char *lcstr,
|
|
|
|
*rcstr;
|
|
|
|
int32 result;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2011-06-09 03:24:27 +08:00
|
|
|
/*
|
|
|
|
* We must do our str_tolower calls with DEFAULT_COLLATION_OID, not the
|
|
|
|
* input collation as you might expect. This is so that the behavior of
|
|
|
|
* citext's equality and hashing functions is not collation-dependent. We
|
|
|
|
* should change this once the core infrastructure is able to cope with
|
|
|
|
* collation-dependent equality and hashing functions.
|
|
|
|
*/
|
|
|
|
|
|
|
|
lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
|
|
|
|
rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
result = varstr_cmp(lcstr, strlen(lcstr),
|
2011-02-09 05:04:18 +08:00
|
|
|
rcstr, strlen(rcstr),
|
|
|
|
collid);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
pfree(lcstr);
|
|
|
|
pfree(rcstr);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
return result;
|
2008-07-30 02:31:20 +08:00
|
|
|
}
|
|
|
|
|
2017-09-19 20:31:45 +08:00
|
|
|
/*
|
|
|
|
* citext_pattern_cmp()
|
|
|
|
* Internal character-by-character comparison function for citext strings.
|
|
|
|
* Returns int32 negative, zero, or positive.
|
|
|
|
*/
|
|
|
|
static int32
|
|
|
|
internal_citext_pattern_cmp(text *left, text *right, Oid collid)
|
|
|
|
{
|
|
|
|
char *lcstr,
|
|
|
|
*rcstr;
|
|
|
|
int llen,
|
|
|
|
rlen;
|
|
|
|
int32 result;
|
|
|
|
|
|
|
|
lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
|
|
|
|
rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);
|
|
|
|
|
|
|
|
llen = strlen(lcstr);
|
|
|
|
rlen = strlen(rcstr);
|
|
|
|
|
|
|
|
result = memcmp((void *) lcstr, (void *) rcstr, Min(llen, rlen));
|
|
|
|
if (result == 0)
|
|
|
|
{
|
|
|
|
if (llen < rlen)
|
|
|
|
result = -1;
|
|
|
|
else if (llen > rlen)
|
|
|
|
result = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
pfree(lcstr);
|
|
|
|
pfree(rcstr);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2008-07-30 02:31:20 +08:00
|
|
|
/*
|
2009-06-11 22:49:15 +08:00
|
|
|
* ==================
|
|
|
|
* INDEXING FUNCTIONS
|
|
|
|
* ==================
|
2008-07-30 02:31:20 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_cmp);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_cmp(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2009-06-11 22:49:15 +08:00
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
int32 result;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2011-02-09 05:04:18 +08:00
|
|
|
result = citextcmp(left, right, PG_GET_COLLATION());
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_RETURN_INT32(result);
|
2008-07-30 02:31:20 +08:00
|
|
|
}
|
|
|
|
|
2017-09-19 20:31:45 +08:00
|
|
|
PG_FUNCTION_INFO_V1(citext_pattern_cmp);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_pattern_cmp(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
int32 result;
|
|
|
|
|
|
|
|
result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION());
|
|
|
|
|
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
|
|
|
|
|
|
|
PG_RETURN_INT32(result);
|
|
|
|
}
|
|
|
|
|
2008-07-30 02:31:20 +08:00
|
|
|
PG_FUNCTION_INFO_V1(citext_hash);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_hash(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2009-06-11 22:49:15 +08:00
|
|
|
text *txt = PG_GETARG_TEXT_PP(0);
|
|
|
|
char *str;
|
|
|
|
Datum result;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2011-06-09 03:24:27 +08:00
|
|
|
str = str_tolower(VARDATA_ANY(txt), VARSIZE_ANY_EXHDR(txt), DEFAULT_COLLATION_OID);
|
2009-06-11 22:49:15 +08:00
|
|
|
result = hash_any((unsigned char *) str, strlen(str));
|
|
|
|
pfree(str);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
/* Avoid leaking memory for toasted inputs */
|
|
|
|
PG_FREE_IF_COPY(txt, 0);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_RETURN_DATUM(result);
|
2008-07-30 02:31:20 +08:00
|
|
|
}
|
|
|
|
|
2018-11-24 02:24:45 +08:00
|
|
|
PG_FUNCTION_INFO_V1(citext_hash_extended);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_hash_extended(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
text *txt = PG_GETARG_TEXT_PP(0);
|
|
|
|
uint64 seed = PG_GETARG_INT64(1);
|
|
|
|
char *str;
|
|
|
|
Datum result;
|
|
|
|
|
|
|
|
str = str_tolower(VARDATA_ANY(txt), VARSIZE_ANY_EXHDR(txt), DEFAULT_COLLATION_OID);
|
|
|
|
result = hash_any_extended((unsigned char *) str, strlen(str), seed);
|
|
|
|
pfree(str);
|
|
|
|
|
|
|
|
/* Avoid leaking memory for toasted inputs */
|
|
|
|
PG_FREE_IF_COPY(txt, 0);
|
|
|
|
|
|
|
|
PG_RETURN_DATUM(result);
|
|
|
|
}
|
|
|
|
|
2008-07-30 02:31:20 +08:00
|
|
|
/*
|
2009-06-11 22:49:15 +08:00
|
|
|
* ==================
|
|
|
|
* OPERATOR FUNCTIONS
|
|
|
|
* ==================
|
2008-07-30 02:31:20 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_eq);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_eq(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2009-06-11 22:49:15 +08:00
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
char *lcstr,
|
|
|
|
*rcstr;
|
|
|
|
bool result;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
/* We can't compare lengths in advance of downcasing ... */
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2011-06-09 03:24:27 +08:00
|
|
|
lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
|
|
|
|
rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
/*
|
|
|
|
* 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);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
pfree(lcstr);
|
|
|
|
pfree(rcstr);
|
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_RETURN_BOOL(result);
|
2008-07-30 02:31:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_ne);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_ne(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2009-06-11 22:49:15 +08:00
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
char *lcstr,
|
|
|
|
*rcstr;
|
|
|
|
bool result;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
/* We can't compare lengths in advance of downcasing ... */
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2011-06-09 03:24:27 +08:00
|
|
|
lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
|
|
|
|
rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
/*
|
|
|
|
* 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);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
pfree(lcstr);
|
|
|
|
pfree(rcstr);
|
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_RETURN_BOOL(result);
|
2008-07-30 02:31:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_lt);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_lt(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2009-06-11 22:49:15 +08:00
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
bool result;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2011-02-09 05:04:18 +08:00
|
|
|
result = citextcmp(left, right, PG_GET_COLLATION()) < 0;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_RETURN_BOOL(result);
|
2008-07-30 02:31:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_le);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_le(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2009-06-11 22:49:15 +08:00
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
bool result;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2011-02-09 05:04:18 +08:00
|
|
|
result = citextcmp(left, right, PG_GET_COLLATION()) <= 0;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_RETURN_BOOL(result);
|
2008-07-30 02:31:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_gt);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_gt(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2009-06-11 22:49:15 +08:00
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
bool result;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2011-02-09 05:04:18 +08:00
|
|
|
result = citextcmp(left, right, PG_GET_COLLATION()) > 0;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_RETURN_BOOL(result);
|
2008-07-30 02:31:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_ge);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_ge(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2009-06-11 22:49:15 +08:00
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
bool result;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2011-02-09 05:04:18 +08:00
|
|
|
result = citextcmp(left, right, PG_GET_COLLATION()) >= 0;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_RETURN_BOOL(result);
|
2008-07-30 02:31:20 +08:00
|
|
|
}
|
|
|
|
|
2017-09-19 20:31:45 +08:00
|
|
|
PG_FUNCTION_INFO_V1(citext_pattern_lt);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_pattern_lt(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
bool result;
|
|
|
|
|
|
|
|
result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) < 0;
|
|
|
|
|
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
|
|
|
|
|
|
|
PG_RETURN_BOOL(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_pattern_le);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_pattern_le(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
bool result;
|
|
|
|
|
|
|
|
result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) <= 0;
|
|
|
|
|
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
|
|
|
|
|
|
|
PG_RETURN_BOOL(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_pattern_gt);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_pattern_gt(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
bool result;
|
|
|
|
|
|
|
|
result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) > 0;
|
|
|
|
|
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
|
|
|
|
|
|
|
PG_RETURN_BOOL(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_pattern_ge);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_pattern_ge(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
bool result;
|
|
|
|
|
|
|
|
result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) >= 0;
|
|
|
|
|
|
|
|
PG_FREE_IF_COPY(left, 0);
|
|
|
|
PG_FREE_IF_COPY(right, 1);
|
|
|
|
|
|
|
|
PG_RETURN_BOOL(result);
|
|
|
|
}
|
|
|
|
|
2008-07-30 02:31:20 +08:00
|
|
|
/*
|
2009-06-11 22:49:15 +08:00
|
|
|
* ===================
|
|
|
|
* AGGREGATE FUNCTIONS
|
|
|
|
* ===================
|
2008-07-30 02:31:20 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_smaller);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_smaller(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2009-06-11 22:49:15 +08:00
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
text *result;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2011-02-09 05:04:18 +08:00
|
|
|
result = citextcmp(left, right, PG_GET_COLLATION()) < 0 ? left : right;
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_RETURN_TEXT_P(result);
|
2008-07-30 02:31:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(citext_larger);
|
|
|
|
|
|
|
|
Datum
|
|
|
|
citext_larger(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2009-06-11 22:49:15 +08:00
|
|
|
text *left = PG_GETARG_TEXT_PP(0);
|
|
|
|
text *right = PG_GETARG_TEXT_PP(1);
|
|
|
|
text *result;
|
2008-07-30 02:31:20 +08:00
|
|
|
|
2011-02-09 05:04:18 +08:00
|
|
|
result = citextcmp(left, right, PG_GET_COLLATION()) > 0 ? left : right;
|
2009-06-11 22:49:15 +08:00
|
|
|
PG_RETURN_TEXT_P(result);
|
2008-07-30 02:31:20 +08:00
|
|
|
}
|