blob: 71492103a3ba51bf1bb377a6f72add0180e3e020 [file] [log] [blame]
#include "postgres.h"
#include <stdlib.h>
#include <locale.h>
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "fmgr.h"
#include "lib/stringinfo.h"
#include "nodes/nodeFuncs.h"
#include "nodes/pg_list.h"
#include "nodes/primnodes.h"
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
#include "orafunc.h"
#include "builtins.h"
/*
* Source code for nlssort is taken from postgresql-nls-string
* package by Jan Pazdziora
*/
static char *lc_collate_cache = NULL;
static int multiplication = 1;
text *def_locale = NULL;
PG_FUNCTION_INFO_V1(ora_lnnvl);
Datum
ora_lnnvl(PG_FUNCTION_ARGS)
{
if (PG_ARGISNULL(0))
PG_RETURN_BOOL(true);
PG_RETURN_BOOL(!PG_GETARG_BOOL(0));
}
PG_FUNCTION_INFO_V1(ora_concat);
Datum
ora_concat(PG_FUNCTION_ARGS)
{
text *t1;
text *t2;
int l1;
int l2;
text *result;
if (PG_ARGISNULL(0) && PG_ARGISNULL(1))
PG_RETURN_NULL();
if (PG_ARGISNULL(0))
PG_RETURN_DATUM(PG_GETARG_DATUM(1));
if (PG_ARGISNULL(1))
PG_RETURN_DATUM(PG_GETARG_DATUM(0));
t1 = PG_GETARG_TEXT_PP(0);
t2 = PG_GETARG_TEXT_PP(1);
l1 = VARSIZE_ANY_EXHDR(t1);
l2 = VARSIZE_ANY_EXHDR(t2);
result = palloc(l1+l2+VARHDRSZ);
memcpy(VARDATA(result), VARDATA_ANY(t1), l1);
memcpy(VARDATA(result) + l1, VARDATA_ANY(t2), l2);
SET_VARSIZE(result, l1 + l2 + VARHDRSZ);
PG_RETURN_TEXT_P(result);
}
PG_FUNCTION_INFO_V1(ora_nvl);
Datum
ora_nvl(PG_FUNCTION_ARGS)
{
if (!PG_ARGISNULL(0))
PG_RETURN_DATUM(PG_GETARG_DATUM(0));
if (!PG_ARGISNULL(1))
PG_RETURN_DATUM(PG_GETARG_DATUM(1));
PG_RETURN_NULL();
}
PG_FUNCTION_INFO_V1(ora_nvl2);
Datum
ora_nvl2(PG_FUNCTION_ARGS)
{
if (!PG_ARGISNULL(0))
{
if (!PG_ARGISNULL(1))
PG_RETURN_DATUM(PG_GETARG_DATUM(1));
}
else
{
if (!PG_ARGISNULL(2))
PG_RETURN_DATUM(PG_GETARG_DATUM(2));
}
PG_RETURN_NULL();
}
PG_FUNCTION_INFO_V1(ora_set_nls_sort);
Datum
ora_set_nls_sort(PG_FUNCTION_ARGS)
{
text *arg = PG_GETARG_TEXT_P(0);
if (def_locale != NULL)
{
pfree(def_locale);
def_locale = NULL;
}
def_locale = (text*) MemoryContextAlloc(TopMemoryContext, VARSIZE(arg));
memcpy(def_locale, arg, VARSIZE(arg));
PG_RETURN_VOID();
}
static text*
_nls_run_strxfrm(text *string, text *locale)
{
char *string_str;
int string_len;
char *locale_str = NULL;
int locale_len = 0;
text *result;
char *tmp = NULL;
size_t size = 0;
size_t rest = 0;
int changed_locale = 0;
/*
* Save the default, server-wide locale setting.
* It should not change during the life-span of the server so it
* is safe to save it only once, during the first invocation.
*/
if (!lc_collate_cache)
{
if ((lc_collate_cache = setlocale(LC_COLLATE, NULL)))
/* Make a copy of the locale name string. */
lc_collate_cache = strdup(lc_collate_cache);
if (!lc_collate_cache)
elog(ERROR, "failed to retrieve the default LC_COLLATE value");
}
/*
* To run strxfrm, we need a zero-terminated strings.
*/
string_len = VARSIZE_ANY_EXHDR(string);
if (string_len < 0)
return NULL;
string_str = palloc(string_len + 1);
memcpy(string_str, VARDATA_ANY(string), string_len);
*(string_str + string_len) = '\0';
if (locale)
{
locale_len = VARSIZE_ANY_EXHDR(locale);
}
/*
* If different than default locale is requested, call setlocale.
*/
if (locale_len > 0
&& (strncmp(lc_collate_cache, VARDATA_ANY(locale), locale_len)
|| *(lc_collate_cache + locale_len) != '\0'))
{
locale_str = palloc(locale_len + 1);
memcpy(locale_str, VARDATA_ANY(locale), locale_len);
*(locale_str + locale_len) = '\0';
/*
* Try to set correct locales.
* If setlocale failed, we know the default stayed the same,
* co we can safely elog.
*/
if (!setlocale(LC_COLLATE, locale_str))
elog(ERROR, "failed to set the requested LC_COLLATE value [%s]", locale_str);
changed_locale = 1;
}
/*
* We do TRY / CATCH / END_TRY to catch ereport / elog that might
* happen during palloc. Ereport during palloc would not be
* nice since it would leave the server with changed locales
* setting, resulting in bad things.
*/
PG_TRY();
{
/*
* Text transformation.
* Increase the buffer until the strxfrm is able to fit.
*/
size = string_len * multiplication + 1;
tmp = palloc(size + VARHDRSZ);
rest = strxfrm(tmp + VARHDRSZ, string_str, size);
while (rest >= size)
{
pfree(tmp);
size = rest + 1;
tmp = palloc(size + VARHDRSZ);
rest = strxfrm(tmp + VARHDRSZ, string_str, size);
/*
* Cache the multiplication factor so that the next
* time we start with better value.
*/
if (string_len)
multiplication = (rest / string_len) + 2;
}
}
PG_CATCH ();
{
if (changed_locale) {
/*
* Set original locale
*/
if (!setlocale(LC_COLLATE, lc_collate_cache))
elog(FATAL, "failed to set back the default LC_COLLATE value [%s]", lc_collate_cache);
}
}
PG_END_TRY ();
if (changed_locale)
{
/*
* Set original locale
*/
if (!setlocale(LC_COLLATE, lc_collate_cache))
elog(FATAL, "failed to set back the default LC_COLLATE value [%s]", lc_collate_cache);
pfree(locale_str);
}
pfree(string_str);
/*
* If the multiplication factor went down, reset it.
*/
if (string_len && rest < string_len * multiplication / 4)
multiplication = (rest / string_len) + 1;
result = (text *) tmp;
SET_VARSIZE(result, rest + VARHDRSZ);
return result;
}
PG_FUNCTION_INFO_V1(ora_nlssort);
Datum
ora_nlssort(PG_FUNCTION_ARGS)
{
text *locale;
text *result;
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
if (PG_ARGISNULL(1))
{
if (def_locale != NULL)
locale = def_locale;
else
{
locale = palloc(VARHDRSZ);
SET_VARSIZE(locale, VARHDRSZ);
}
}
else
{
locale = PG_GETARG_TEXT_PP(1);
}
result = _nls_run_strxfrm(PG_GETARG_TEXT_PP(0), locale);
if (! result)
PG_RETURN_NULL();
PG_RETURN_BYTEA_P(result);
}
PG_FUNCTION_INFO_V1(ora_decode);
/*
* decode(lhs, [rhs, ret], ..., [default])
*/
Datum
ora_decode(PG_FUNCTION_ARGS)
{
int nargs;
int i;
int retarg;
/* default value is last arg or NULL. */
nargs = PG_NARGS();
if (nargs % 2 == 0)
{
retarg = nargs - 1;
nargs -= 1; /* ignore the last argument */
}
else
retarg = -1; /* NULL */
if (PG_ARGISNULL(0))
{
for (i = 1; i < nargs; i += 2)
{
if (PG_ARGISNULL(i))
{
retarg = i + 1;
break;
}
}
}
else
{
FmgrInfo *eq;
/*
* On first call, get the input type's operator '=' and save at
* fn_extra.
*/
if (fcinfo->flinfo->fn_extra == NULL)
{
MemoryContext oldctx;
Oid typid = get_fn_expr_argtype(fcinfo->flinfo, 0);
Oid eqoid = equality_oper_funcid(typid);
oldctx = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
eq = palloc(sizeof(FmgrInfo));
fmgr_info(eqoid, eq);
MemoryContextSwitchTo(oldctx);
fcinfo->flinfo->fn_extra = eq;
}
else
eq = fcinfo->flinfo->fn_extra;
for (i = 1; i < nargs; i += 2)
{
FunctionCallInfoData func;
Datum result;
if (PG_ARGISNULL(i))
continue;
InitFunctionCallInfoData(func, eq, 2, NULL, NULL);
func.arg[0] = PG_GETARG_DATUM(0);
func.arg[1] = PG_GETARG_DATUM(i);
func.argnull[0] = false;
func.argnull[1] = false;
result = FunctionCallInvoke(&func);
if (!func.isnull && DatumGetBool(result))
{
retarg = i + 1;
break;
}
}
}
if (retarg < 0 || PG_ARGISNULL(retarg))
PG_RETURN_NULL();
else
PG_RETURN_DATUM(PG_GETARG_DATUM(retarg));
}
#if PG_VERSION_NUM >= 90100
Oid
equality_oper_funcid(Oid argtype)
{
Oid eq;
get_sort_group_operators(argtype, false, true, false, NULL, &eq, NULL, NULL);
return get_opcode(eq);
}
#elif PG_VERSION_NUM >= 80400
Oid
equality_oper_funcid(Oid argtype)
{
Oid eq;
get_sort_group_operators(argtype, false, true, false, NULL, &eq, NULL);
return get_opcode(eq);
}
#endif
/*
* dump(anyexpr [,format])
*
* the dump function returns a varchar2 value that includes the datatype code,
* the length in bytes, and the internal representation of the expression.
*/
PG_FUNCTION_INFO_V1(orafce_dump);
static void
appendDatum(StringInfo str, const void *ptr, size_t length, int format)
{
if (!PointerIsValid(ptr))
appendStringInfoChar(str, ':');
else
{
const unsigned char *s = (const unsigned char *) ptr;
const char *formatstr;
size_t i;
switch (format)
{
case 8:
formatstr = "%ho";
break;
case 10:
formatstr = "%hu";
break;
case 16:
formatstr = "%hx";
break;
case 17:
formatstr = "%hc";
break;
default:
elog(ERROR, "unknown format");
formatstr = NULL; /* quite compiler */
}
/* append a byte array with the specified format */
for (i = 0; i < length; i++)
{
if (i > 0)
appendStringInfoChar(str, ',');
/* print only ANSI visible chars */
if (format == 17 && (iscntrl(s[i]) || !isascii(s[i])))
appendStringInfoChar(str, '?');
else
appendStringInfo(str, formatstr, s[i]);
}
}
}
Datum
orafce_dump(PG_FUNCTION_ARGS)
{
Oid valtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
List *args;
int16 typlen;
bool typbyval;
Size length;
Datum value;
int format;
StringInfoData str;
if (!fcinfo->flinfo || !fcinfo->flinfo->fn_expr)
elog(ERROR, "function is called from invalid context");
if (PG_ARGISNULL(0))
elog(ERROR, "argument is NULL");
value = PG_GETARG_DATUM(0);
format = PG_GETARG_IF_EXISTS(1, INT32, 10);
args = ((FuncExpr *) fcinfo->flinfo->fn_expr)->args;
valtype = exprType((Node *) list_nth(args, 0));
get_typlenbyval(valtype, &typlen, &typbyval);
length = datumGetSize(value, typbyval, typlen);
initStringInfo(&str);
appendStringInfo(&str, "Typ=%d Len=%d: ", valtype, (int) length);
if (!typbyval)
{
appendDatum(&str, DatumGetPointer(value), length, format);
}
else if (length <= 1)
{
char v = DatumGetChar(value);
appendDatum(&str, &v, sizeof(char), format);
}
else if (length <= 2)
{
int16 v = DatumGetInt16(value);
appendDatum(&str, &v, sizeof(int16), format);
}
else if (length <= 4)
{
int32 v = DatumGetInt32(value);
appendDatum(&str, &v, sizeof(int32), format);
}
else
{
int64 v = DatumGetInt64(value);
appendDatum(&str, &v, sizeof(int64), format);
}
PG_RETURN_TEXT_P(cstring_to_text(str.data));
}