Re: patch: function xmltable

Alvaro Herrera <alvherre@2ndquadrant.com>

From: Alvaro Herrera <alvherre@2ndquadrant.com>
To: Pavel Stehule <pavel.stehule@gmail.com>
Cc: Craig Ringer <craig@2ndquadrant.com>, PostgreSQL Hackers <pgsql-hackers@postgresql.org>
Date: 2017-01-16T23:30:00Z
Lists: pgsql-hackers
In case this still matters, I think GetValue should look more or less
like this (untested):


/*
 * Return the value for column number 'colnum' for the current row.  If column
 * -1 is requested, return representation of the whole row.
 *
 * This leaks memory, so be sure to reset often the context in which it's
 * called.
 */
static Datum
XmlTableGetValue(TableExprState *state, int colnum, bool *isnull)
{
#ifdef USE_LIBXML
	XmlTableBuilderData *xtCxt;
	Datum		result = (Datum) 0;
	xmlNodePtr	cur;
	char	   *cstr = NULL;
	volatile xmlXPathObjectPtr xpathobj;

	xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");

	Assert(xtCxt->xpathobj &&
		   xtCxt->xpathobj->type == XPATH_NODESET &&
		   xtCxt->xpathobj->nodesetval != NULL);

	/* Propagate context related error context to libxml2 */
	xmlSetStructuredErrorFunc((void *) xtCxt->xmlerrcxt, xml_errorHandler);

	cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
	if (cur->type != XML_ELEMENT_NODE)
		elog(ERROR, "unexpected xmlNode type");

	/* Handle whole row case the easy way. */
	if (colnum == -1)
	{
		text	   *txt;

		txt = xml_xmlnodetoxmltype(cur, xtCxt->xmlerrcxt);
		result = InputFunctionCall(&state->in_functions[0],
								   text_to_cstring(txt),
								   state->typioparams[0],
								   -1);
		*isnull = false;

		return result;
	}

	Assert(xtCxt->xpathscomp[colnum] != NULL);

	xpathobj = NULL;
	PG_TRY();
	{
		Form_pg_attribute attr;

		attr = state->resultSlot->tts_tupleDescriptor->attrs[colnum];

		/* Set current node as entry point for XPath evaluation */
		xmlXPathSetContextNode(cur, xtCxt->xpathcxt);

		/* Evaluate column path */
		xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
		if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
			xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
						"could not create XPath object");

		if (xpathobj->type == XPATH_NODESET)
		{
			int			count;
			Oid			targettypid = attr->atttypid;

			if (xpathobj->nodesetval != NULL)
				count = xpathobj->nodesetval->nodeNr;

			/*
			 * There are four possible cases, depending on the number of
			 * nodes returned by the XPath expression and the type of the
			 * target column: a) XPath returns no nodes.  b) One node is
			 * returned, and column is of type XML.  c) One node, column type
			 * other than XML.  d) Multiple nodes are returned.
			 */
			if (xpathobj->nodesetval == NULL)
			{
				*isnull = true;
			}
			else if (count == 1 && targettypid == XMLOID)
			{
				textstr = xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[0],
											   xtCxt->xmlerrcxt);
				cstr = text_to_cstring(textstr);
			}
			else if (count == 1)
			{
				xmlChar    *str;

				str = xmlNodeListGetString(xtCxt->doc,
										   xpathobj->nodesetval->nodeTab[0]->xmlChildrenNode,
										   1);
				if (str)
				{
					PG_TRY();
					{
						cstr = pstrdup(str);
					}
					PG_CATCH();
					{
						xmlFree(str);
						PG_RE_THROW();
					}
					PG_END_TRY();
					xmlFree(str);
				}
				else
					cstr = pstrdup("");
			}
			else
			{
				StringInfoData buf;
				int			i;

				Assert(count > 1);

				/*
				 * When evaluating the XPath expression returns multiple
				 * nodes, the result is the concatenation of them all.
				 * The target type must be XML.
				 */
				if (targettypid != XMLOID)
					ereport(ERROR,
							(errcode(ERRCODE_CARDINALITY_VIOLATION),
							 errmsg("more than one value returned by column XPath expression")));

				initStringInfo(&buf);
				for (i = 0; i < count; i++)
					/* worth freeing the text here?  Naahh ... */
					appendStringInfoText(&buf,
										 xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
															  xtCxt->xmlerrcxt));
				cstr = buf.data;
			}
		}
		else if (xpathobj->type == XPATH_STRING)
		{
			cstr = (char *) xpathobj->stringval;
			*isnull = false;
		}
		else
			elog(ERROR, "unexpected XPath object type %u", xpathobj->type);

		/*
		 * By here, either cstr contains the result value, or the isnull flag
		 * has been set.
		 */
		Assert(cstr || *isnull);

		if (!*isnull)
			result = InputFunctionCall(&state->in_functions[colnum],
									   cstr,
									   state->typioparams[colnum],
									   attr->atttypmod);
	}
	PG_CATCH();
	{
		if (xpathobj != NULL)
			xmlXPathFreeObject(xpathobj);
		PG_RE_THROW();
	}
	PG_END_TRY();

	if (xpathobj)
		xmlXPathFreeObject(xpathobj);

	return result;
#else
	NO_XML_SUPPORT();
#endif   /* not USE_LIBXML */
}

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services


Commits

  1. Support XMLTABLE query expression

  2. Create <sect3> in the functions-xml section

  3. Improve handling of "UPDATE ... SET (column_list) = row_constructor".

  4. Add xmlexists function