mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-31 10:30:33 +03:00 
			
		
		
		
	It seems potentially useful to label our shared libraries with version information, now that a facility exists for retrieving that. This patch labels them with the PG_VERSION string. There was some discussion about using semantic versioning conventions, but that doesn't seem terribly helpful for modules with no SQL-level presence; and for those that do have SQL objects, we typically expect them to support multiple revisions of the SQL definitions, so it'd still not be very helpful. I did not label any of src/test/modules/. It seems unnecessary since we don't install those, and besides there ought to be someplace that still provides test coverage for the original PG_MODULE_MAGIC macro. Author: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/dd4d1b59-d0fe-49d5-b28f-1e463b68fa32@gmail.com
		
			
				
	
	
		
			299 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			299 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include "postgres.h"
 | |
| 
 | |
| #include <math.h>
 | |
| 
 | |
| #include "fmgr.h"
 | |
| #include "plperl.h"
 | |
| #include "utils/fmgrprotos.h"
 | |
| #include "utils/jsonb.h"
 | |
| 
 | |
| PG_MODULE_MAGIC_EXT(
 | |
| 					.name = "jsonb_plperl",
 | |
| 					.version = PG_VERSION
 | |
| );
 | |
| 
 | |
| 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);
 | |
| 
 | |
| 		default:
 | |
| 			if (!SvOK(in))
 | |
| 			{
 | |
| 				out.type = jbvNull;
 | |
| 			}
 | |
| 			else 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 = int64_to_numeric(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 those, so we have to reject them here
 | |
| 				 * explicitly.
 | |
| 				 */
 | |
| 				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);
 | |
| }
 |