From: Peter Eisentraut Date: Thu, 5 Aug 2010 04:21:54 +0000 (+0000) Subject: Add xmlexists function X-Git-Tag: REL9_1_ALPHA1~126 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=641459f26954b04f74d098a758b716297b6554ea;p=postgresql Add xmlexists function by Mike Fowler, reviewed by Peter Eisentraut --- diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index dbe0d46069..07ff132cbb 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,4 +1,4 @@ - + Functions and Operators @@ -8554,10 +8554,19 @@ SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab; ]]> + - + XML Predicates + + The expressions described in this section check properties + of xml values. + + + + IS DOCUMENT + IS DOCUMENT @@ -8574,6 +8583,48 @@ SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab; between documents and content fragments. + + + XMLEXISTS + + + XMLEXISTS + + + +XMLEXISTS(text PASSING BY REF xml BY REF) + + + + The function xmlexists returns true if the + XPath expression in the first argument returns any nodes, and + false otherwise. (If either argument is null, the result is + null.) + + + + Example: + TorontoOttawa'); + + xmlexists +------------ + t +(1 row) +]]> + + + + The BY REF clauses have no effect in + PostgreSQL, but are allowed for SQL conformance and compatibility + with other implementations. Per SQL standard, the + first BY REF is required, the second is + optional. Also note that the SQL standard specifies + the xmlexists construct to take an XQuery + expression as first argument, but PostgreSQL currently only + supports XPath, which is a subset of XQuery. + + diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 07ee2a8349..d9aebb0d72 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.714 2010/07/25 23:21:21 rhaas Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.715 2010/08/05 04:21:53 petere Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -425,6 +425,7 @@ static TypeName *TableFuncTypeName(List *columns); %type xml_attribute_el %type xml_attribute_list xml_attributes %type xml_root_version opt_xml_root_standalone +%type xmlexists_argument %type document_or_content %type xml_whitespace_option @@ -511,13 +512,13 @@ static TypeName *TableFuncTypeName(List *columns); OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER - PARSER PARTIAL PARTITION PASSWORD PLACING PLANS POSITION + PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PRIOR PRIVILEGES PROCEDURAL PROCEDURE QUOTE - RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX + RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE @@ -539,7 +540,7 @@ static TypeName *TableFuncTypeName(List *columns); WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE - XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLFOREST XMLPARSE + XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE XMLPI XMLROOT XMLSERIALIZE YEAR_P YES_P @@ -9839,6 +9840,21 @@ func_expr: func_name '(' ')' over_clause { $$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, $8, @1); } + | XMLEXISTS '(' c_expr xmlexists_argument ')' + { + /* xmlexists(A PASSING [BY REF] B [BY REF]) is + * converted to xmlexists(A, B)*/ + FuncCall *n = makeNode(FuncCall); + n->funcname = SystemFuncName("xmlexists"); + n->args = list_make2($3, $4); + n->agg_order = NIL; + n->agg_star = FALSE; + n->agg_distinct = FALSE; + n->func_variadic = FALSE; + n->over = NULL; + n->location = @1; + $$ = (Node *)n; + } | XMLFOREST '(' xml_attribute_list ')' { $$ = makeXmlExpr(IS_XMLFOREST, NULL, $3, NIL, @1); @@ -9929,6 +9945,27 @@ xml_whitespace_option: PRESERVE WHITESPACE_P { $$ = TRUE; } | /*EMPTY*/ { $$ = FALSE; } ; +/* We allow several variants for SQL and other compatibility. */ +xmlexists_argument: + PASSING c_expr + { + $$ = $2; + } + | PASSING c_expr BY REF + { + $$ = $2; + } + | PASSING BY REF c_expr + { + $$ = $4; + } + | PASSING BY REF c_expr BY REF + { + $$ = $4; + } + ; + + /* * Window Definitions */ @@ -10999,6 +11036,7 @@ unreserved_keyword: | PARSER | PARTIAL | PARTITION + | PASSING | PASSWORD | PLANS | PRECEDING @@ -11015,6 +11053,7 @@ unreserved_keyword: | REASSIGN | RECHECK | RECURSIVE + | REF | REINDEX | RELATIVE_P | RELEASE @@ -11148,6 +11187,7 @@ col_name_keyword: | XMLATTRIBUTES | XMLCONCAT | XMLELEMENT + | XMLEXISTS | XMLFOREST | XMLPARSE | XMLPI diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index eaf5b4d550..6587f4e4fc 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.98 2010/07/06 19:18:58 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.99 2010/08/05 04:21:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -3295,24 +3295,20 @@ xml_xmlnodetoxmltype(xmlNodePtr cur) /* - * Evaluate XPath expression and return array of XML values. + * Common code for xpath() and xmlexists() * - * As we have no support of XQuery sequences yet, this function seems - * to be the most useful one (array of XML functions plays a role of - * some kind of substitution for XQuery sequences). + * Evaluate XPath expression and return number of nodes in res_items + * and array of XML values in astate. * * It is up to the user to ensure that the XML passed is in fact * an XML document - XPath doesn't work easily on fragments without * a context node being known. */ -Datum -xpath(PG_FUNCTION_ARGS) -{ #ifdef USE_LIBXML - text *xpath_expr_text = PG_GETARG_TEXT_P(0); - xmltype *data = PG_GETARG_XML_P(1); - ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2); - ArrayBuildState *astate = NULL; +static void +xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces, + int *res_nitems, ArrayBuildState **astate) +{ xmlParserCtxtPtr ctxt = NULL; xmlDocPtr doc = NULL; xmlXPathContextPtr xpathctx = NULL; @@ -3324,7 +3320,6 @@ xpath(PG_FUNCTION_ARGS) xmlChar *string; xmlChar *xpath_expr; int i; - int res_nitems; int ndim; Datum *ns_names_uris; bool *ns_names_uris_nulls; @@ -3339,7 +3334,7 @@ xpath(PG_FUNCTION_ARGS) * ARRAY[ARRAY['myns', 'http://example.com'], ARRAY['myns2', * 'http://example2.com']]. */ - ndim = ARR_NDIM(namespaces); + ndim = namespaces ? ARR_NDIM(namespaces) : 0; if (ndim != 0) { int *dims; @@ -3439,6 +3434,13 @@ xpath(PG_FUNCTION_ARGS) xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, "invalid XPath expression"); + /* + * Version 2.6.27 introduces a function named + * xmlXPathCompiledEvalToBoolean, which would be enough for + * xmlexists, but we can derive the existence by whether any + * nodes are returned, thereby preventing a library version + * upgrade and keeping the code the same. + */ xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx); if (xpathobj == NULL) /* TODO: reason? */ xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, @@ -3446,21 +3448,22 @@ xpath(PG_FUNCTION_ARGS) /* return empty array in cases when nothing is found */ if (xpathobj->nodesetval == NULL) - res_nitems = 0; + *res_nitems = 0; else - res_nitems = xpathobj->nodesetval->nodeNr; + *res_nitems = xpathobj->nodesetval->nodeNr; - if (res_nitems) + if (*res_nitems && astate) { + *astate = NULL; for (i = 0; i < xpathobj->nodesetval->nodeNr; i++) { Datum elem; bool elemisnull = false; elem = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i])); - astate = accumArrayResult(astate, elem, - elemisnull, XMLOID, - CurrentMemoryContext); + *astate = accumArrayResult(*astate, elem, + elemisnull, XMLOID, + CurrentMemoryContext); } } } @@ -3485,6 +3488,28 @@ xpath(PG_FUNCTION_ARGS) xmlXPathFreeContext(xpathctx); xmlFreeDoc(doc); xmlFreeParserCtxt(ctxt); +} +#endif /* USE_LIBXML */ + +/* + * Evaluate XPath expression and return array of XML values. + * + * As we have no support of XQuery sequences yet, this function seems + * to be the most useful one (array of XML functions plays a role of + * some kind of substitution for XQuery sequences). + */ +Datum +xpath(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *xpath_expr_text = PG_GETARG_TEXT_P(0); + xmltype *data = PG_GETARG_XML_P(1); + ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2); + int res_nitems; + ArrayBuildState *astate; + + xpath_internal(xpath_expr_text, data, namespaces, + &res_nitems, &astate); if (res_nitems == 0) PG_RETURN_ARRAYTYPE_P(construct_empty_array(XMLOID)); @@ -3495,3 +3520,24 @@ xpath(PG_FUNCTION_ARGS) return 0; #endif } + +/* + * Determines if the node specified by the supplied XPath exists + * in a given XML document, returning a boolean. + */ +Datum xmlexists(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *xpath_expr_text = PG_GETARG_TEXT_P(0); + xmltype *data = PG_GETARG_XML_P(1); + int res_nitems; + + xpath_internal(xpath_expr_text, data, NULL, + &res_nitems, NULL); + + PG_RETURN_BOOL(res_nitems > 0); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index ac52eb18bc..a1a223b227 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.588 2010/07/16 02:15:54 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.589 2010/08/05 04:21:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201007151 +#define CATALOG_VERSION_NO 201008051 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index a505770c4f..8b9e1db581 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.573 2010/07/29 20:09:25 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.574 2010/08/05 04:21:54 petere Exp $ * * NOTES * The script catalog/genbki.pl reads this file and generates .bki @@ -4391,6 +4391,9 @@ DESCR("evaluate XPath expression, with namespaces support"); DATA(insert OID = 2932 ( xpath PGNSP PGUID 14 1 0 0 f f f t f i 2 0 143 "25 142" _null_ _null_ _null_ _null_ "select pg_catalog.xpath($1, $2, ''{}''::pg_catalog.text[])" _null_ _null_ _null_ )); DESCR("evaluate XPath expression"); +DATA(insert OID = 2614 ( xmlexists PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "25 142" _null_ _null_ _null_ _null_ xmlexists _null_ _null_ _null_ )); +DESCR("test XML value against XPath expression"); + /* uuid */ DATA(insert OID = 2952 ( uuid_in PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2950 "2275" _null_ _null_ _null_ _null_ uuid_in _null_ _null_ _null_ )); DESCR("I/O"); diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 5065bd609e..271c5ca7b6 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -11,7 +11,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.12 2010/02/12 17:33:21 tgl Exp $ + * $PostgreSQL: pgsql/src/include/parser/kwlist.h,v 1.13 2010/08/05 04:21:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -280,6 +280,7 @@ PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD) PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD) PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD) PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD) +PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD) PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD) PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD) PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD) @@ -301,6 +302,7 @@ PG_KEYWORD("real", REAL, COL_NAME_KEYWORD) PG_KEYWORD("reassign", REASSIGN, UNRESERVED_KEYWORD) PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD) PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD) +PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD) PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD) PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD) PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD) @@ -413,6 +415,7 @@ PG_KEYWORD("xml", XML_P, UNRESERVED_KEYWORD) PG_KEYWORD("xmlattributes", XMLATTRIBUTES, COL_NAME_KEYWORD) PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD) PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD) +PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD) PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD) PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD) PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD) diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h index 7543cbb6ee..6815e266c7 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.31 2010/03/03 17:29:45 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.32 2010/08/05 04:21:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -37,6 +37,7 @@ extern Datum texttoxml(PG_FUNCTION_ARGS); extern Datum xmltotext(PG_FUNCTION_ARGS); extern Datum xmlvalidate(PG_FUNCTION_ARGS); extern Datum xpath(PG_FUNCTION_ARGS); +extern Datum xmlexists(PG_FUNCTION_ARGS); extern Datum table_to_xml(PG_FUNCTION_ARGS); extern Datum query_to_xml(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index ecca5896a7..439fef4877 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -502,3 +502,52 @@ SELECT xpath('//b', 'one two three etc'); {two,etc} (1 row) +-- Test xmlexists evaluation +SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'); + xmlexists +----------- + f +(1 row) + +SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'); + xmlexists +----------- + t +(1 row) + +INSERT INTO xmltest VALUES (4, 'BudvarfreeCarlinglots'::xml); +INSERT INTO xmltest VALUES (5, 'MolsonfreeCarlinglots'::xml); +INSERT INTO xmltest VALUES (6, 'BudvarfreeCarlinglots'::xml); +INSERT INTO xmltest VALUES (7, 'MolsonfreeCarlinglots'::xml); +SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data); + count +------- + 0 +(1 row) + +SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF); + count +------- + 0 +(1 row) + +SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data); + count +------- + 2 +(1 row) + +SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data); + count +------- + 1 +(1 row) + +CREATE TABLE query ( expr TEXT ); +INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']'); +SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data); + count +------- + 2 +(1 row) + diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index d542b0689a..d15e50a1b9 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -456,3 +456,72 @@ LINE 1: SELECT xpath('//b', 'one two three etc'... ^ DETAIL: This functionality requires the server to be built with libxml support. HINT: You need to rebuild PostgreSQL using --with-libxml. +-- Test xmlexists evaluation +SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'); +ERROR: unsupported XML feature +LINE 1: ...sts('//town[text() = ''Toronto'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'); +ERROR: unsupported XML feature +LINE 1: ...sts('//town[text() = ''Cwmbran'']' PASSING BY REF 'BudvarfreeCarlinglots'::xml); +ERROR: unsupported XML feature +LINE 1: INSERT INTO xmltest VALUES (4, 'BudvarMolsonfreeCarlinglots'::xml); +ERROR: unsupported XML feature +LINE 1: INSERT INTO xmltest VALUES (5, 'MolsonBudvarfreeCarlinglots'::xml); +ERROR: unsupported XML feature +LINE 1: INSERT INTO xmltest VALUES (6, 'MolsonfreeCarlinglots'::xml); +ERROR: unsupported XML feature +LINE 1: INSERT INTO xmltest VALUES (7, 'number one'); SELECT xpath('//loc:piece/@id', 'number one', ARRAY[ARRAY['loc', 'http://127.0.0.1']]); SELECT xpath('//b', 'one two three etc'); + +-- Test xmlexists evaluation +SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'); +SELECT xmlexists('//town[text() = ''Cwmbran'']' PASSING BY REF 'Bidford-on-AvonCwmbranBristol'); + +INSERT INTO xmltest VALUES (4, 'BudvarfreeCarlinglots'::xml); +INSERT INTO xmltest VALUES (5, 'MolsonfreeCarlinglots'::xml); +INSERT INTO xmltest VALUES (6, 'BudvarfreeCarlinglots'::xml); +INSERT INTO xmltest VALUES (7, 'MolsonfreeCarlinglots'::xml); + + +SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING data); +SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beer' PASSING BY REF data BY REF); +SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers' PASSING BY REF data); +SELECT COUNT(id) FROM xmltest WHERE xmlexists('/menu/beers/name[text() = ''Molson'']' PASSING BY REF data); + +CREATE TABLE query ( expr TEXT ); +INSERT INTO query VALUES ('/menu/beers/cost[text() = ''lots'']'); +SELECT COUNT(id) FROM xmltest, query WHERE xmlexists(expr PASSING BY REF data);