mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	We include <float.h> in every place that needs isnan(), because MSVC
used to require it.  However, since MSVC 2013 that's no longer necessary
(cf. commit cec8394b5c), so we can retire the inclusion to a
version-specific stanza in win32_port.h, where it doesn't need to
pollute random .c files.  The header is of course still needed in a few
places for other reasons.
I (Álvaro) removed float.h from a few more files than in Emre's original
patch.  This doesn't break the build in my system, but we'll see what
the buildfarm has to say about it all.
Author: Emre Hasegeli
Discussion: https://postgr.es/m/CAE2gYzyc0+5uG+Cd9-BSL7NKC8LSHLNg1Aq2=8ubjnUwut4_iw@mail.gmail.com
		
	
		
			
				
	
	
		
			304 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
#include "postgres.h"
 | 
						|
 | 
						|
#include <math.h>
 | 
						|
 | 
						|
/* Defined by Perl */
 | 
						|
#undef _
 | 
						|
 | 
						|
#include "fmgr.h"
 | 
						|
#include "plperl.h"
 | 
						|
#include "plperl_helpers.h"
 | 
						|
#include "utils/jsonb.h"
 | 
						|
#include "utils/fmgrprotos.h"
 | 
						|
 | 
						|
PG_MODULE_MAGIC;
 | 
						|
 | 
						|
static SV  *Jsonb_to_SV(JsonbContainer *jsonb);
 | 
						|
static JsonbValue *SV_to_JsonbValue(SV *obj, JsonbParseState **ps, bool is_elem);
 | 
						|
 | 
						|
 | 
						|
static SV  *
 | 
						|
JsonbValue_to_SV(JsonbValue *jbv)
 | 
						|
{
 | 
						|
	dTHX;
 | 
						|
 | 
						|
	switch (jbv->type)
 | 
						|
	{
 | 
						|
		case jbvBinary:
 | 
						|
			return Jsonb_to_SV(jbv->val.binary.data);
 | 
						|
 | 
						|
		case jbvNumeric:
 | 
						|
			{
 | 
						|
				char	   *str = DatumGetCString(DirectFunctionCall1(numeric_out,
 | 
						|
																	  NumericGetDatum(jbv->val.numeric)));
 | 
						|
				SV		   *result = newSVnv(SvNV(cstr2sv(str)));
 | 
						|
 | 
						|
				pfree(str);
 | 
						|
				return result;
 | 
						|
			}
 | 
						|
 | 
						|
		case jbvString:
 | 
						|
			{
 | 
						|
				char	   *str = pnstrdup(jbv->val.string.val,
 | 
						|
										   jbv->val.string.len);
 | 
						|
				SV		   *result = cstr2sv(str);
 | 
						|
 | 
						|
				pfree(str);
 | 
						|
				return result;
 | 
						|
			}
 | 
						|
 | 
						|
		case jbvBool:
 | 
						|
			return newSVnv(SvNV(jbv->val.boolean ? &PL_sv_yes : &PL_sv_no));
 | 
						|
 | 
						|
		case jbvNull:
 | 
						|
			return newSV(0);
 | 
						|
 | 
						|
		default:
 | 
						|
			elog(ERROR, "unexpected jsonb value type: %d", jbv->type);
 | 
						|
			return NULL;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static SV  *
 | 
						|
Jsonb_to_SV(JsonbContainer *jsonb)
 | 
						|
{
 | 
						|
	dTHX;
 | 
						|
	JsonbValue	v;
 | 
						|
	JsonbIterator *it;
 | 
						|
	JsonbIteratorToken r;
 | 
						|
 | 
						|
	it = JsonbIteratorInit(jsonb);
 | 
						|
	r = JsonbIteratorNext(&it, &v, true);
 | 
						|
 | 
						|
	switch (r)
 | 
						|
	{
 | 
						|
		case WJB_BEGIN_ARRAY:
 | 
						|
			if (v.val.array.rawScalar)
 | 
						|
			{
 | 
						|
				JsonbValue	tmp;
 | 
						|
 | 
						|
				if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM ||
 | 
						|
					(r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY ||
 | 
						|
					(r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE)
 | 
						|
					elog(ERROR, "unexpected jsonb token: %d", r);
 | 
						|
 | 
						|
				return JsonbValue_to_SV(&v);
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				AV		   *av = newAV();
 | 
						|
 | 
						|
				while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
 | 
						|
				{
 | 
						|
					if (r == WJB_ELEM)
 | 
						|
						av_push(av, JsonbValue_to_SV(&v));
 | 
						|
				}
 | 
						|
 | 
						|
				return newRV((SV *) av);
 | 
						|
			}
 | 
						|
 | 
						|
		case WJB_BEGIN_OBJECT:
 | 
						|
			{
 | 
						|
				HV		   *hv = newHV();
 | 
						|
 | 
						|
				while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
 | 
						|
				{
 | 
						|
					if (r == WJB_KEY)
 | 
						|
					{
 | 
						|
						/* json key in v, json value in val */
 | 
						|
						JsonbValue	val;
 | 
						|
 | 
						|
						if (JsonbIteratorNext(&it, &val, true) == WJB_VALUE)
 | 
						|
						{
 | 
						|
							SV		   *value = JsonbValue_to_SV(&val);
 | 
						|
 | 
						|
							(void) hv_store(hv,
 | 
						|
											v.val.string.val, v.val.string.len,
 | 
						|
											value, 0);
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				return newRV((SV *) hv);
 | 
						|
			}
 | 
						|
 | 
						|
		default:
 | 
						|
			elog(ERROR, "unexpected jsonb token: %d", r);
 | 
						|
			return NULL;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static JsonbValue *
 | 
						|
AV_to_JsonbValue(AV *in, JsonbParseState **jsonb_state)
 | 
						|
{
 | 
						|
	dTHX;
 | 
						|
	SSize_t		pcount = av_len(in) + 1;
 | 
						|
	SSize_t		i;
 | 
						|
 | 
						|
	pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
 | 
						|
 | 
						|
	for (i = 0; i < pcount; i++)
 | 
						|
	{
 | 
						|
		SV		  **value = av_fetch(in, i, FALSE);
 | 
						|
 | 
						|
		if (value)
 | 
						|
			(void) SV_to_JsonbValue(*value, jsonb_state, true);
 | 
						|
	}
 | 
						|
 | 
						|
	return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL);
 | 
						|
}
 | 
						|
 | 
						|
static JsonbValue *
 | 
						|
HV_to_JsonbValue(HV *obj, JsonbParseState **jsonb_state)
 | 
						|
{
 | 
						|
	dTHX;
 | 
						|
	JsonbValue	key;
 | 
						|
	SV		   *val;
 | 
						|
	char	   *kstr;
 | 
						|
	I32			klen;
 | 
						|
 | 
						|
	key.type = jbvString;
 | 
						|
 | 
						|
	pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL);
 | 
						|
 | 
						|
	(void) hv_iterinit(obj);
 | 
						|
 | 
						|
	while ((val = hv_iternextsv(obj, &kstr, &klen)))
 | 
						|
	{
 | 
						|
		key.val.string.val = pnstrdup(kstr, klen);
 | 
						|
		key.val.string.len = klen;
 | 
						|
		pushJsonbValue(jsonb_state, WJB_KEY, &key);
 | 
						|
		(void) SV_to_JsonbValue(val, jsonb_state, false);
 | 
						|
	}
 | 
						|
 | 
						|
	return pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL);
 | 
						|
}
 | 
						|
 | 
						|
static JsonbValue *
 | 
						|
SV_to_JsonbValue(SV *in, JsonbParseState **jsonb_state, bool is_elem)
 | 
						|
{
 | 
						|
	dTHX;
 | 
						|
	JsonbValue	out;			/* result */
 | 
						|
 | 
						|
	/* Dereference references recursively. */
 | 
						|
	while (SvROK(in))
 | 
						|
		in = SvRV(in);
 | 
						|
 | 
						|
	switch (SvTYPE(in))
 | 
						|
	{
 | 
						|
		case SVt_PVAV:
 | 
						|
			return AV_to_JsonbValue((AV *) in, jsonb_state);
 | 
						|
 | 
						|
		case SVt_PVHV:
 | 
						|
			return HV_to_JsonbValue((HV *) in, jsonb_state);
 | 
						|
 | 
						|
		case SVt_NULL:
 | 
						|
			out.type = jbvNull;
 | 
						|
			break;
 | 
						|
 | 
						|
		default:
 | 
						|
			if (SvUOK(in))
 | 
						|
			{
 | 
						|
				/*
 | 
						|
				 * If UV is >=64 bits, we have no better way to make this
 | 
						|
				 * happen than converting to text and back.  Given the low
 | 
						|
				 * usage of UV in Perl code, it's not clear it's worth working
 | 
						|
				 * hard to provide alternate code paths.
 | 
						|
				 */
 | 
						|
				const char *strval = SvPV_nolen(in);
 | 
						|
 | 
						|
				out.type = jbvNumeric;
 | 
						|
				out.val.numeric =
 | 
						|
					DatumGetNumeric(DirectFunctionCall3(numeric_in,
 | 
						|
														CStringGetDatum(strval),
 | 
						|
														ObjectIdGetDatum(InvalidOid),
 | 
						|
														Int32GetDatum(-1)));
 | 
						|
			}
 | 
						|
			else if (SvIOK(in))
 | 
						|
			{
 | 
						|
				IV			ival = SvIV(in);
 | 
						|
 | 
						|
				out.type = jbvNumeric;
 | 
						|
				out.val.numeric =
 | 
						|
					DatumGetNumeric(DirectFunctionCall1(int8_numeric,
 | 
						|
														Int64GetDatum((int64) ival)));
 | 
						|
			}
 | 
						|
			else if (SvNOK(in))
 | 
						|
			{
 | 
						|
				double		nval = SvNV(in);
 | 
						|
 | 
						|
				/*
 | 
						|
				 * jsonb doesn't allow infinity or NaN (per JSON
 | 
						|
				 * specification), but the numeric type that is used for the
 | 
						|
				 * storage accepts NaN, so we have to prevent it here
 | 
						|
				 * explicitly.  We don't really have to check for isinf()
 | 
						|
				 * here, as numeric doesn't allow it and it would be caught
 | 
						|
				 * later, but it makes for a nicer error message.
 | 
						|
				 */
 | 
						|
				if (isinf(nval))
 | 
						|
					ereport(ERROR,
 | 
						|
							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 | 
						|
							 (errmsg("cannot convert infinity to jsonb"))));
 | 
						|
				if (isnan(nval))
 | 
						|
					ereport(ERROR,
 | 
						|
							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 | 
						|
							 (errmsg("cannot convert NaN to jsonb"))));
 | 
						|
 | 
						|
				out.type = jbvNumeric;
 | 
						|
				out.val.numeric =
 | 
						|
					DatumGetNumeric(DirectFunctionCall1(float8_numeric,
 | 
						|
														Float8GetDatum(nval)));
 | 
						|
			}
 | 
						|
			else if (SvPOK(in))
 | 
						|
			{
 | 
						|
				out.type = jbvString;
 | 
						|
				out.val.string.val = sv2cstr(in);
 | 
						|
				out.val.string.len = strlen(out.val.string.val);
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				/*
 | 
						|
				 * XXX It might be nice if we could include the Perl type in
 | 
						|
				 * the error message.
 | 
						|
				 */
 | 
						|
				ereport(ERROR,
 | 
						|
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 | 
						|
						 (errmsg("cannot transform this Perl type to jsonb"))));
 | 
						|
				return NULL;
 | 
						|
			}
 | 
						|
	}
 | 
						|
 | 
						|
	/* Push result into 'jsonb_state' unless it is a raw scalar. */
 | 
						|
	return *jsonb_state
 | 
						|
		? pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, &out)
 | 
						|
		: memcpy(palloc(sizeof(JsonbValue)), &out, sizeof(JsonbValue));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PG_FUNCTION_INFO_V1(jsonb_to_plperl);
 | 
						|
 | 
						|
Datum
 | 
						|
jsonb_to_plperl(PG_FUNCTION_ARGS)
 | 
						|
{
 | 
						|
	dTHX;
 | 
						|
	Jsonb	   *in = PG_GETARG_JSONB_P(0);
 | 
						|
	SV		   *sv = Jsonb_to_SV(&in->root);
 | 
						|
 | 
						|
	return PointerGetDatum(sv);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
PG_FUNCTION_INFO_V1(plperl_to_jsonb);
 | 
						|
 | 
						|
Datum
 | 
						|
plperl_to_jsonb(PG_FUNCTION_ARGS)
 | 
						|
{
 | 
						|
	dTHX;
 | 
						|
	JsonbParseState *jsonb_state = NULL;
 | 
						|
	SV		   *in = (SV *) PG_GETARG_POINTER(0);
 | 
						|
	JsonbValue *out = SV_to_JsonbValue(in, &jsonb_state, true);
 | 
						|
	Jsonb	   *result = JsonbValueToJsonb(out);
 | 
						|
 | 
						|
	PG_RETURN_JSONB_P(result);
 | 
						|
}
 |