mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-04 00:02:52 -05: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);
 | 
						|
}
 |