mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-29 22:49:41 +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);
 | |
| }
 |