mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-27 00:12:01 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			298 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			298 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*-------------------------------------------------------------------------
 | |
|  *
 | |
|  * hstore_subs.c
 | |
|  *	  Subscripting support functions for hstore.
 | |
|  *
 | |
|  * This is a great deal simpler than array_subs.c, because the result of
 | |
|  * subscripting an hstore is just a text string (the value for the key).
 | |
|  * We do not need to support array slicing notation, nor multiple subscripts.
 | |
|  * Less obviously, because the subscript result is never a SQL container
 | |
|  * type, there will never be any nested-assignment scenarios, so we do not
 | |
|  * need a fetch_old function.  In turn, that means we can drop the
 | |
|  * check_subscripts function and just let the fetch and assign functions
 | |
|  * do everything.
 | |
|  *
 | |
|  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 | |
|  * Portions Copyright (c) 1994, Regents of the University of California
 | |
|  *
 | |
|  *
 | |
|  * IDENTIFICATION
 | |
|  *	  contrib/hstore/hstore_subs.c
 | |
|  *
 | |
|  *-------------------------------------------------------------------------
 | |
|  */
 | |
| #include "postgres.h"
 | |
| 
 | |
| #include "executor/execExpr.h"
 | |
| #include "hstore.h"
 | |
| #include "nodes/nodeFuncs.h"
 | |
| #include "nodes/subscripting.h"
 | |
| #include "parser/parse_coerce.h"
 | |
| #include "parser/parse_expr.h"
 | |
| #include "utils/builtins.h"
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Finish parse analysis of a SubscriptingRef expression for hstore.
 | |
|  *
 | |
|  * Verify there's just one subscript, coerce it to text,
 | |
|  * and set the result type of the SubscriptingRef node.
 | |
|  */
 | |
| static void
 | |
| hstore_subscript_transform(SubscriptingRef *sbsref,
 | |
| 						   List *indirection,
 | |
| 						   ParseState *pstate,
 | |
| 						   bool isSlice,
 | |
| 						   bool isAssignment)
 | |
| {
 | |
| 	A_Indices  *ai;
 | |
| 	Node	   *subexpr;
 | |
| 
 | |
| 	/* We support only single-subscript, non-slice cases */
 | |
| 	if (isSlice || list_length(indirection) != 1)
 | |
| 		ereport(ERROR,
 | |
| 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 | |
| 				 errmsg("hstore allows only one subscript"),
 | |
| 				 parser_errposition(pstate,
 | |
| 									exprLocation((Node *) indirection))));
 | |
| 
 | |
| 	/* Transform the subscript expression to type text */
 | |
| 	ai = linitial_node(A_Indices, indirection);
 | |
| 	Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
 | |
| 
 | |
| 	subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
 | |
| 	/* If it's not text already, try to coerce */
 | |
| 	subexpr = coerce_to_target_type(pstate,
 | |
| 									subexpr, exprType(subexpr),
 | |
| 									TEXTOID, -1,
 | |
| 									COERCION_ASSIGNMENT,
 | |
| 									COERCE_IMPLICIT_CAST,
 | |
| 									-1);
 | |
| 	if (subexpr == NULL)
 | |
| 		ereport(ERROR,
 | |
| 				(errcode(ERRCODE_DATATYPE_MISMATCH),
 | |
| 				 errmsg("hstore subscript must have type text"),
 | |
| 				 parser_errposition(pstate, exprLocation(ai->uidx))));
 | |
| 
 | |
| 	/* ... and store the transformed subscript into the SubscriptRef node */
 | |
| 	sbsref->refupperindexpr = list_make1(subexpr);
 | |
| 	sbsref->reflowerindexpr = NIL;
 | |
| 
 | |
| 	/* Determine the result type of the subscripting operation; always text */
 | |
| 	sbsref->refrestype = TEXTOID;
 | |
| 	sbsref->reftypmod = -1;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Evaluate SubscriptingRef fetch for hstore.
 | |
|  *
 | |
|  * Source container is in step's result variable (it's known not NULL, since
 | |
|  * we set fetch_strict to true), and the subscript expression is in the
 | |
|  * upperindex[] array.
 | |
|  */
 | |
| static void
 | |
| hstore_subscript_fetch(ExprState *state,
 | |
| 					   ExprEvalStep *op,
 | |
| 					   ExprContext *econtext)
 | |
| {
 | |
| 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 | |
| 	HStore	   *hs;
 | |
| 	text	   *key;
 | |
| 	HEntry	   *entries;
 | |
| 	int			idx;
 | |
| 	text	   *out;
 | |
| 
 | |
| 	/* Should not get here if source hstore is null */
 | |
| 	Assert(!(*op->resnull));
 | |
| 
 | |
| 	/* Check for null subscript */
 | |
| 	if (sbsrefstate->upperindexnull[0])
 | |
| 	{
 | |
| 		*op->resnull = true;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* OK, fetch/detoast the hstore and subscript */
 | |
| 	hs = DatumGetHStoreP(*op->resvalue);
 | |
| 	key = DatumGetTextPP(sbsrefstate->upperindex[0]);
 | |
| 
 | |
| 	/* The rest is basically the same as hstore_fetchval() */
 | |
| 	entries = ARRPTR(hs);
 | |
| 	idx = hstoreFindKey(hs, NULL,
 | |
| 						VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key));
 | |
| 
 | |
| 	if (idx < 0 || HSTORE_VALISNULL(entries, idx))
 | |
| 	{
 | |
| 		*op->resnull = true;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	out = cstring_to_text_with_len(HSTORE_VAL(entries, STRPTR(hs), idx),
 | |
| 								   HSTORE_VALLEN(entries, idx));
 | |
| 
 | |
| 	*op->resvalue = PointerGetDatum(out);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Evaluate SubscriptingRef assignment for hstore.
 | |
|  *
 | |
|  * Input container (possibly null) is in result area, replacement value is in
 | |
|  * SubscriptingRefState's replacevalue/replacenull.
 | |
|  */
 | |
| static void
 | |
| hstore_subscript_assign(ExprState *state,
 | |
| 						ExprEvalStep *op,
 | |
| 						ExprContext *econtext)
 | |
| {
 | |
| 	SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
 | |
| 	text	   *key;
 | |
| 	Pairs		p;
 | |
| 	HStore	   *out;
 | |
| 
 | |
| 	/* Check for null subscript */
 | |
| 	if (sbsrefstate->upperindexnull[0])
 | |
| 		ereport(ERROR,
 | |
| 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
 | |
| 				 errmsg("hstore subscript in assignment must not be null")));
 | |
| 
 | |
| 	/* OK, fetch/detoast the subscript */
 | |
| 	key = DatumGetTextPP(sbsrefstate->upperindex[0]);
 | |
| 
 | |
| 	/* Create a Pairs entry for subscript + replacement value */
 | |
| 	p.needfree = false;
 | |
| 	p.key = VARDATA_ANY(key);
 | |
| 	p.keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key));
 | |
| 
 | |
| 	if (sbsrefstate->replacenull)
 | |
| 	{
 | |
| 		p.vallen = 0;
 | |
| 		p.isnull = true;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		text	   *val = DatumGetTextPP(sbsrefstate->replacevalue);
 | |
| 
 | |
| 		p.val = VARDATA_ANY(val);
 | |
| 		p.vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(val));
 | |
| 		p.isnull = false;
 | |
| 	}
 | |
| 
 | |
| 	if (*op->resnull)
 | |
| 	{
 | |
| 		/* Just build a one-element hstore (cf. hstore_from_text) */
 | |
| 		out = hstorePairs(&p, 1, p.keylen + p.vallen);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		/*
 | |
| 		 * Otherwise, merge the new key into the hstore.  Based on
 | |
| 		 * hstore_concat.
 | |
| 		 */
 | |
| 		HStore	   *hs = DatumGetHStoreP(*op->resvalue);
 | |
| 		int			s1count = HS_COUNT(hs);
 | |
| 		int			outcount = 0;
 | |
| 		int			vsize;
 | |
| 		char	   *ps1,
 | |
| 				   *bufd,
 | |
| 				   *pd;
 | |
| 		HEntry	   *es1,
 | |
| 				   *ed;
 | |
| 		int			s1idx;
 | |
| 		int			s2idx;
 | |
| 
 | |
| 		/* Allocate result without considering possibility of duplicate */
 | |
| 		vsize = CALCDATASIZE(s1count + 1, VARSIZE(hs) + p.keylen + p.vallen);
 | |
| 		out = palloc(vsize);
 | |
| 		SET_VARSIZE(out, vsize);
 | |
| 		HS_SETCOUNT(out, s1count + 1);
 | |
| 
 | |
| 		ps1 = STRPTR(hs);
 | |
| 		bufd = pd = STRPTR(out);
 | |
| 		es1 = ARRPTR(hs);
 | |
| 		ed = ARRPTR(out);
 | |
| 
 | |
| 		for (s1idx = s2idx = 0; s1idx < s1count || s2idx < 1; ++outcount)
 | |
| 		{
 | |
| 			int			difference;
 | |
| 
 | |
| 			if (s1idx >= s1count)
 | |
| 				difference = 1;
 | |
| 			else if (s2idx >= 1)
 | |
| 				difference = -1;
 | |
| 			else
 | |
| 			{
 | |
| 				int			s1keylen = HSTORE_KEYLEN(es1, s1idx);
 | |
| 				int			s2keylen = p.keylen;
 | |
| 
 | |
| 				if (s1keylen == s2keylen)
 | |
| 					difference = memcmp(HSTORE_KEY(es1, ps1, s1idx),
 | |
| 										p.key,
 | |
| 										s1keylen);
 | |
| 				else
 | |
| 					difference = (s1keylen > s2keylen) ? 1 : -1;
 | |
| 			}
 | |
| 
 | |
| 			if (difference >= 0)
 | |
| 			{
 | |
| 				HS_ADDITEM(ed, bufd, pd, p);
 | |
| 				++s2idx;
 | |
| 				if (difference == 0)
 | |
| 					++s1idx;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				HS_COPYITEM(ed, bufd, pd,
 | |
| 							HSTORE_KEY(es1, ps1, s1idx),
 | |
| 							HSTORE_KEYLEN(es1, s1idx),
 | |
| 							HSTORE_VALLEN(es1, s1idx),
 | |
| 							HSTORE_VALISNULL(es1, s1idx));
 | |
| 				++s1idx;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		HS_FINALIZE(out, outcount, bufd, pd);
 | |
| 	}
 | |
| 
 | |
| 	*op->resvalue = PointerGetDatum(out);
 | |
| 	*op->resnull = false;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Set up execution state for an hstore subscript operation.
 | |
|  */
 | |
| static void
 | |
| hstore_exec_setup(const SubscriptingRef *sbsref,
 | |
| 				  SubscriptingRefState *sbsrefstate,
 | |
| 				  SubscriptExecSteps *methods)
 | |
| {
 | |
| 	/* Assert we are dealing with one subscript */
 | |
| 	Assert(sbsrefstate->numlower == 0);
 | |
| 	Assert(sbsrefstate->numupper == 1);
 | |
| 	/* We can't check upperprovided[0] here, but it must be true */
 | |
| 
 | |
| 	/* Pass back pointers to appropriate step execution functions */
 | |
| 	methods->sbs_check_subscripts = NULL;
 | |
| 	methods->sbs_fetch = hstore_subscript_fetch;
 | |
| 	methods->sbs_assign = hstore_subscript_assign;
 | |
| 	methods->sbs_fetch_old = NULL;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * hstore_subscript_handler
 | |
|  *		Subscripting handler for hstore.
 | |
|  */
 | |
| PG_FUNCTION_INFO_V1(hstore_subscript_handler);
 | |
| Datum
 | |
| hstore_subscript_handler(PG_FUNCTION_ARGS)
 | |
| {
 | |
| 	static const SubscriptRoutines sbsroutines = {
 | |
| 		.transform = hstore_subscript_transform,
 | |
| 		.exec_setup = hstore_exec_setup,
 | |
| 		.fetch_strict = true,	/* fetch returns NULL for NULL inputs */
 | |
| 		.fetch_leakproof = true,	/* fetch returns NULL for bad subscript */
 | |
| 		.store_leakproof = false	/* ... but assignment throws error */
 | |
| 	};
 | |
| 
 | |
| 	PG_RETURN_POINTER(&sbsroutines);
 | |
| }
 |