diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b52407822d..af72a07326 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -10468,7 +10468,8 @@ SELECT xml_is_well_formed_document('test', To deal with default (anonymous) namespaces, do something like this: test', - ARRAY[ARRAY['mydefns', 'http://example.com']]); +SELECT xpath('//b/text()', 'test', + ARRAY[ARRAY['', 'http://example.com']]); xpath -------- @@ -10562,8 +10563,7 @@ SELECT xpath_exists('/my:a/text()', 'test The optional XMLNAMESPACES clause is a comma-separated list of namespaces. It specifies the XML namespaces used in - the document and their aliases. A default namespace specification - is not currently supported. + the document and their aliases. diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 1fb018416e..b60a3cfe0d 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -29,7 +29,7 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \ tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \ tsvector.o tsvector_op.o tsvector_parser.o \ txid.o uuid.o varbit.o varchar.o varlena.o version.o \ - windowfuncs.o xid.o xml.o + windowfuncs.o xid.o xml.o xpath_parser.o like.o: like.c like_match.c diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 24229c2dff..90c51ea1c6 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -90,7 +90,7 @@ #include "utils/rel.h" #include "utils/syscache.h" #include "utils/xml.h" - +#include "utils/xpath_parser.h" /* GUC variables */ int xmlbinary; @@ -187,6 +187,7 @@ typedef struct XmlTableBuilderData xmlXPathCompExprPtr xpathcomp; xmlXPathObjectPtr xpathobj; xmlXPathCompExprPtr *xpathscomp; + bool with_default_ns; } XmlTableBuilderData; #endif @@ -227,6 +228,7 @@ const TableFuncRoutine XmlTableRoutine = #define NAMESPACE_XSI "http://www.w3.org/2001/XMLSchema-instance" #define NAMESPACE_SQLXML "http://standards.iso.org/iso/9075/2003/sqlxml" +#define DEFAULT_NAMESPACE_NAME "pgdefnamespace.pgsqlxml.internal" #ifdef USE_LIBXML @@ -3849,6 +3851,7 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces, int ndim; Datum *ns_names_uris; bool *ns_names_uris_nulls; + bool with_default_ns = false; int ns_count; /* @@ -3898,7 +3901,6 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces, errmsg("empty XPath expression"))); string = pg_xmlCharStrndup(datastr, len); - xpath_expr = pg_xmlCharStrndup(VARDATA_ANY(xpath_expr_text), xpath_len); xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); @@ -3941,6 +3943,26 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("neither namespace name nor URI may be null"))); ns_name = TextDatumGetCString(ns_names_uris[i * 2]); + + /* Don't allow same namespace as out internal default namespace name */ + if (strcmp(ns_name, DEFAULT_NAMESPACE_NAME) == 0) + ereport(ERROR, + (errcode(ERRCODE_RESERVED_NAME), + errmsg("cannot to use \"%s\" as namespace name", + DEFAULT_NAMESPACE_NAME), + errdetail("\"%s\" is reserved for internal purpose", + DEFAULT_NAMESPACE_NAME))); + if (*ns_name == '\0') + { + if (with_default_ns) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only one default namespace is allowed"))); + + with_default_ns = true; + ns_name = DEFAULT_NAMESPACE_NAME; + } + ns_uri = TextDatumGetCString(ns_names_uris[i * 2 + 1]); if (xmlXPathRegisterNs(xpathctx, (xmlChar *) ns_name, @@ -3951,6 +3973,16 @@ xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces, } } + if (with_default_ns) + { + StringInfoData str; + + transformXPath(&str, text_to_cstring(xpath_expr_text), DEFAULT_NAMESPACE_NAME); + xpath_expr = pg_xmlCharStrndup(str.data, str.len); + } + else + xpath_expr = pg_xmlCharStrndup(VARDATA_ANY(xpath_expr_text), xpath_len); + xpathcomp = xmlXPathCompile(xpath_expr); if (xpathcomp == NULL || xmlerrcxt->err_occurred) xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR, @@ -4195,6 +4227,7 @@ XmlTableInitOpaque(TableFuncScanState *state, int natts) xtCxt->magic = XMLTABLE_CONTEXT_MAGIC; xtCxt->natts = natts; xtCxt->xpathscomp = palloc0(sizeof(xmlXPathCompExprPtr) * natts); + xtCxt->with_default_ns = false; xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); @@ -4287,6 +4320,7 @@ XmlTableSetDocument(TableFuncScanState *state, Datum value) #endif /* not USE_LIBXML */ } + /* * XmlTableSetNamespace * Add a namespace declaration @@ -4297,12 +4331,25 @@ XmlTableSetNamespace(TableFuncScanState *state, char *name, char *uri) #ifdef USE_LIBXML XmlTableBuilderData *xtCxt; - if (name == NULL) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("DEFAULT namespace is not supported"))); xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetNamespace"); + if (name != NULL) + { + /* Don't allow same namespace as out internal default namespace name */ + if (strcmp(name, DEFAULT_NAMESPACE_NAME) == 0) + ereport(ERROR, + (errcode(ERRCODE_RESERVED_NAME), + errmsg("cannot to use \"%s\" as namespace name", + DEFAULT_NAMESPACE_NAME), + errdetail("\"%s\" is reserved for internal purpose", + DEFAULT_NAMESPACE_NAME))); + } + else + { + xtCxt->with_default_ns = true; + name = DEFAULT_NAMESPACE_NAME; + } + if (xmlXPathRegisterNs(xtCxt->xpathcxt, pg_xmlCharStrndup(name, strlen(name)), pg_xmlCharStrndup(uri, strlen(uri)))) @@ -4331,6 +4378,14 @@ XmlTableSetRowFilter(TableFuncScanState *state, char *path) (errcode(ERRCODE_DATA_EXCEPTION), errmsg("row path filter must not be empty string"))); + if (xtCxt->with_default_ns) + { + StringInfoData str; + + transformXPath(&str, path, DEFAULT_NAMESPACE_NAME); + path = str.data; + } + xstr = pg_xmlCharStrndup(path, strlen(path)); xtCxt->xpathcomp = xmlXPathCompile(xstr); @@ -4362,6 +4417,14 @@ XmlTableSetColumnFilter(TableFuncScanState *state, char *path, int colnum) (errcode(ERRCODE_DATA_EXCEPTION), errmsg("column path filter must not be empty string"))); + if (xtCxt->with_default_ns) + { + StringInfoData str; + + transformXPath(&str, path, DEFAULT_NAMESPACE_NAME); + path = str.data; + } + xstr = pg_xmlCharStrndup(path, strlen(path)); xtCxt->xpathscomp[colnum] = xmlXPathCompile(xstr); diff --git a/src/backend/utils/adt/xpath_parser.c b/src/backend/utils/adt/xpath_parser.c new file mode 100644 index 0000000000..f22ec7638b --- /dev/null +++ b/src/backend/utils/adt/xpath_parser.c @@ -0,0 +1,327 @@ +/*------------------------------------------------------------------------- + * + * xpath_parser.c + * XML XPath parser. + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/backend/utils/adt/xpath_parser.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "utils/xpath_parser.h" + +/* + * All PostgreSQL XML related functionality is based on libxml2 library, and + * XPath support is not an exception. However, libxml2 doesn't support + * default namespace for XPath expressions. Because there are not any API + * how to transform or access to parsed XPath expression we have to parse + * XPath here. + * + * Those functionalities are implemented with a simple XPath parser/ + * preprocessor. This XPath parser transforms a XPath expression to another + * XPath expression that can be used by libxml2 XPath evaluation. It doesn't + * replace libxml2 XPath parser or libxml2 XPath expression evaluation. + */ + +#ifdef USE_LIBXML + +/* + * We need to work with XPath expression tokens. When expression starting with + * nodename, then we can use prefix. When default namespace is defined, then we + * should to enhance any nodename and attribute without namespace by default + * namespace. + */ + +typedef enum +{ + XPATH_TOKEN_NONE, + XPATH_TOKEN_NAME, + XPATH_TOKEN_STRING, + XPATH_TOKEN_NUMBER, + XPATH_TOKEN_OTHER +} XPathTokenType; + +typedef struct XPathTokenInfo +{ + XPathTokenType ttype; + char *start; + int length; +} XPathTokenInfo; + +typedef struct ParserData +{ + char *str; + char *cur; + XPathTokenInfo buffer; + bool buffer_is_empty; +} XPathParserData; + +/* Any high-bit-set character is OK (might be part of a multibyte char) */ +#define IS_NODENAME_FIRSTCHAR(c) ((c) == '_' || \ + ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + (IS_HIGHBIT_SET(c))) + +#define IS_NODENAME_CHAR(c) (IS_NODENAME_FIRSTCHAR(c) || (c) == '-' || (c) == '.' || \ + ((c) >= '0' && (c) <= '9')) + + +/* + * Returns next char after last char of token - XPath lexer + */ +static char * +getXPathToken(char *str, XPathTokenInfo * ti) +{ + /* skip initial spaces */ + while (*str == ' ') + str++; + + if (*str != '\0') + { + char c = *str; + + ti->start = str++; + + if (c >= '0' && c <= '9') + { + while (*str >= '0' && *str <= '9') + str++; + if (*str == '.') + { + str++; + while (*str >= '0' && *str <= '9') + str++; + } + ti->ttype = XPATH_TOKEN_NUMBER; + } + else if (IS_NODENAME_FIRSTCHAR(c)) + { + while (IS_NODENAME_CHAR(*str)) + str++; + + ti->ttype = XPATH_TOKEN_NAME; + } + else if (c == '"') + { + while (*str != '\0') + if (*str++ == '"') + break; + + ti->ttype = XPATH_TOKEN_STRING; + } + else + ti->ttype = XPATH_TOKEN_OTHER; + + ti->length = str - ti->start; + } + else + { + ti->start = NULL; + ti->length = 0; + + ti->ttype = XPATH_TOKEN_NONE; + } + + return str; +} + +/* + * reset XPath parser stack + */ +static void +initXPathParser(XPathParserData * parser, char *str) +{ + parser->str = str; + parser->cur = str; + parser->buffer_is_empty = true; +} + +/* + * Returns token from stack or read token + */ +static void +nextXPathToken(XPathParserData * parser, XPathTokenInfo * ti) +{ + if (!parser->buffer_is_empty) + { + memcpy(ti, &parser->buffer, sizeof(XPathTokenInfo)); + parser->buffer_is_empty = true; + } + else + parser->cur = getXPathToken(parser->cur, ti); +} + +/* + * Push token to stack + */ +static void +pushXPathToken(XPathParserData * parser, XPathTokenInfo * ti) +{ + if (!parser->buffer_is_empty) + elog(ERROR, "internal error"); + + memcpy(&parser->buffer, ti, sizeof(XPathTokenInfo)); + parser->buffer_is_empty = false; +} + +/* + * Write token to output string + */ +static void +writeXPathToken(StringInfo str, XPathTokenInfo * ti) +{ + Assert(ti->ttype != XPATH_TOKEN_NONE); + + if (ti->ttype != XPATH_TOKEN_OTHER) + appendBinaryStringInfo(str, ti->start, ti->length); + else + appendStringInfoChar(str, *ti->start); +} + +/* + * This is main part of XPath transformation. It can be called recursivly, + * when XPath expression contains predicates. + */ +static void +_transformXPath(StringInfo str, XPathParserData * parser, + bool inside_predicate, + char *def_namespace_name) +{ + XPathTokenInfo t1, + t2; + bool token_is_tagname = false; + bool token_is_tagattrib = false; + + nextXPathToken(parser, &t1); + + while (t1.ttype != XPATH_TOKEN_NONE) + { + switch (t1.ttype) + { + case XPATH_TOKEN_NUMBER: + case XPATH_TOKEN_STRING: + token_is_tagname = false; + token_is_tagattrib = false; + + writeXPathToken(str, &t1); + nextXPathToken(parser, &t1); + break; + + case XPATH_TOKEN_NAME: + { + bool is_qual_name = false; + + /* inside predicate ignore keywords "and" "or" */ + if (inside_predicate) + { + if ((strncmp(t1.start, "and", 3) == 0 && t1.length == 3) || + (strncmp(t1.start, "or", 2) == 0 && t1.length == 2)) + { + writeXPathToken(str, &t1); + nextXPathToken(parser, &t1); + break; + } + } + + token_is_tagname = true; + nextXPathToken(parser, &t2); + if (t2.ttype == XPATH_TOKEN_OTHER) + { + if (*t2.start == '(') + token_is_tagname = false; + else if (*t2.start == ':') + { + XPathTokenInfo t3; + + nextXPathToken(parser, &t3); + if (t3.ttype == XPATH_TOKEN_OTHER && *t3.start == ':' + && strncmp(t1.start, "attribute", 9) == 0) + { + /* other syntax for attribute, where we should not apply def namespace */ + appendStringInfo(str, "attribute::"); + nextXPathToken(parser, &t1); + token_is_tagattrib = true; + break; + } + else + { + pushXPathToken(parser, &t3); + is_qual_name = true; + } + } + } + + if (token_is_tagname && !token_is_tagattrib + && !is_qual_name && def_namespace_name != NULL) + appendStringInfo(str, "%s:", def_namespace_name); + + token_is_tagattrib = false; + + writeXPathToken(str, &t1); + + if (is_qual_name) + { + writeXPathToken(str, &t2); + nextXPathToken(parser, &t1); + if (t1.ttype == XPATH_TOKEN_NAME) + writeXPathToken(str, &t1); + else + pushXPathToken(parser, &t1); + } + else + pushXPathToken(parser, &t2); + + nextXPathToken(parser, &t1); + } + break; + + case XPATH_TOKEN_OTHER: + { + char c = *t1.start; + + token_is_tagattrib = false; + token_is_tagname = false; + + writeXPathToken(str, &t1); + + if (c == '[') + _transformXPath(str, parser, true, def_namespace_name); + else + { + if (c == ']' && inside_predicate) + return; + + else if (c == '@') + { + nextXPathToken(parser, &t1); + if (t1.ttype == XPATH_TOKEN_NAME) + token_is_tagattrib = true; + + pushXPathToken(parser, &t1); + } + } + nextXPathToken(parser, &t1); + } + break; + + case XPATH_TOKEN_NONE: + elog(ERROR, "should not be here"); + } + } +} + +void +transformXPath(StringInfo str, char *xpath, + char *def_namespace_name) +{ + XPathParserData parser; + + initStringInfo(str); + initXPathParser(&parser, xpath); + _transformXPath(str, &parser, false, def_namespace_name); +} + +#endif diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index bcc585d427..63e04f1353 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -1085,7 +1085,11 @@ SELECT * FROM XMLTABLE(XMLNAMESPACES(DEFAULT 'http://x.y'), '/rows/row' PASSING '10' COLUMNS a int PATH 'a'); -ERROR: DEFAULT namespace is not supported + a +---- + 10 +(1 row) + -- used in prepare statements PREPARE pp AS SELECT xmltable.* @@ -1452,3 +1456,56 @@ SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c 14 (4 rows) +-- default namespaces +CREATE TABLE t1 (id int, doc xml); +INSERT INTO t1 VALUES (5, '50'); +SELECT x.* FROM t1, xmltable(XMLNAMESPACES('http://x.y' AS x), '/x:rows/x:row' PASSING t1.doc COLUMNS data int PATH 'x:a[1][@hoge]') AS x; + data +------ + 50 +(1 row) + +SELECT x.* FROM t1, xmltable(XMLNAMESPACES(DEFAULT 'http://x.y'), '/rows/row' PASSING t1.doc COLUMNS data int PATH 'a[1][@hoge]') AS x; + data +------ + 50 +(1 row) + +SELECT x.* FROM t1, xmltable(XMLNAMESPACES(DEFAULT 'http://x.y'), '/rows/row' PASSING t1.doc COLUMNS data int PATH 'child::a[1][attribute::hoge="haha"]') as x; + data +------ + 50 +(1 row) + +-- should fail +SELECT x.* FROM t1, xmltable(XMLNAMESPACES('http://x.y' AS "pgdefnamespace.pgsqlxml.internal"), '/x:rows/x:row' PASSING t1.doc COLUMNS data int PATH 'x:a[1][@hoge]') AS x; +ERROR: cannot to use "pgdefnamespace.pgsqlxml.internal" as namespace name +DETAIL: "pgdefnamespace.pgsqlxml.internal" is reserved for internal purpose +-- xpath and xpath_exists supports namespaces too +SELECT xpath('/x:rows/x:row/x:a[1][@hoge]', '50', ARRAY[ARRAY['x', 'http://x.y']]); + xpath +-------------------------------------------------- + {"50"} +(1 row) + +SELECT xpath('/rows/row/a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y']]); + xpath +-------------------------------------------------- + {"50"} +(1 row) + +SELECT xpath_exists('/x:rows/x:row/x:a[1][@hoge]', '50', ARRAY[ARRAY['x', 'http://x.y']]); + xpath_exists +-------------- + t +(1 row) + +SELECT xpath_exists('/rows/row/a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y']]); + xpath_exists +-------------- + t +(1 row) + +-- should fail +SELECT xpath_exists('/rows/row/a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y'], ARRAY['', 'http://x.z']]); +ERROR: only one default namespace is allowed diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index d3bd8c91d7..58f9151788 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -1302,3 +1302,59 @@ SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c --- (0 rows) +-- default namespaces +CREATE TABLE t1 (id int, doc xml); +INSERT INTO t1 VALUES (5, '50'); +ERROR: unsupported XML feature +LINE 1: INSERT INTO t1 VALUES (5, '50', ARRAY[ARRAY['x', 'http://x.y']]); +ERROR: unsupported XML feature +LINE 1: SELECT xpath('/x:rows/x:row/x:a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y']]); +ERROR: unsupported XML feature +LINE 1: SELECT xpath('/rows/row/a[1][@hoge]', '50', ARRAY[ARRAY['x', 'http://x.y']]); +ERROR: unsupported XML feature +LINE 1: ...ELECT xpath_exists('/x:rows/x:row/x:a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y']]); +ERROR: unsupported XML feature +LINE 1: SELECT xpath_exists('/rows/row/a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y'], ARRAY['', 'http://x.z']]); +ERROR: unsupported XML feature +LINE 1: SELECT xpath_exists('/rows/row/a[1][@hoge]', '10' COLUMNS a int PATH 'a'); -ERROR: DEFAULT namespace is not supported + a +---- + 10 +(1 row) + -- used in prepare statements PREPARE pp AS SELECT xmltable.* @@ -1432,3 +1436,56 @@ SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c 14 (4 rows) +-- default namespaces +CREATE TABLE t1 (id int, doc xml); +INSERT INTO t1 VALUES (5, '50'); +SELECT x.* FROM t1, xmltable(XMLNAMESPACES('http://x.y' AS x), '/x:rows/x:row' PASSING t1.doc COLUMNS data int PATH 'x:a[1][@hoge]') AS x; + data +------ + 50 +(1 row) + +SELECT x.* FROM t1, xmltable(XMLNAMESPACES(DEFAULT 'http://x.y'), '/rows/row' PASSING t1.doc COLUMNS data int PATH 'a[1][@hoge]') AS x; + data +------ + 50 +(1 row) + +SELECT x.* FROM t1, xmltable(XMLNAMESPACES(DEFAULT 'http://x.y'), '/rows/row' PASSING t1.doc COLUMNS data int PATH 'child::a[1][attribute::hoge="haha"]') as x; + data +------ + 50 +(1 row) + +-- should fail +SELECT x.* FROM t1, xmltable(XMLNAMESPACES('http://x.y' AS "pgdefnamespace.pgsqlxml.internal"), '/x:rows/x:row' PASSING t1.doc COLUMNS data int PATH 'x:a[1][@hoge]') AS x; +ERROR: cannot to use "pgdefnamespace.pgsqlxml.internal" as namespace name +DETAIL: "pgdefnamespace.pgsqlxml.internal" is reserved for internal purpose +-- xpath and xpath_exists supports namespaces too +SELECT xpath('/x:rows/x:row/x:a[1][@hoge]', '50', ARRAY[ARRAY['x', 'http://x.y']]); + xpath +-------------------------------------------------- + {"50"} +(1 row) + +SELECT xpath('/rows/row/a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y']]); + xpath +-------------------------------------------------- + {"50"} +(1 row) + +SELECT xpath_exists('/x:rows/x:row/x:a[1][@hoge]', '50', ARRAY[ARRAY['x', 'http://x.y']]); + xpath_exists +-------------- + t +(1 row) + +SELECT xpath_exists('/rows/row/a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y']]); + xpath_exists +-------------- + t +(1 row) + +-- should fail +SELECT xpath_exists('/rows/row/a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y'], ARRAY['', 'http://x.z']]); +ERROR: only one default namespace is allowed diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index eb4687fb09..e8cff5f22d 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -558,3 +558,23 @@ INSERT INTO xmltest2 VALUES('2', 'D'); SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c'); SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.'); SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54); + +-- default namespaces +CREATE TABLE t1 (id int, doc xml); +INSERT INTO t1 VALUES (5, '50'); + +SELECT x.* FROM t1, xmltable(XMLNAMESPACES('http://x.y' AS x), '/x:rows/x:row' PASSING t1.doc COLUMNS data int PATH 'x:a[1][@hoge]') AS x; +SELECT x.* FROM t1, xmltable(XMLNAMESPACES(DEFAULT 'http://x.y'), '/rows/row' PASSING t1.doc COLUMNS data int PATH 'a[1][@hoge]') AS x; +SELECT x.* FROM t1, xmltable(XMLNAMESPACES(DEFAULT 'http://x.y'), '/rows/row' PASSING t1.doc COLUMNS data int PATH 'child::a[1][attribute::hoge="haha"]') as x; + +-- should fail +SELECT x.* FROM t1, xmltable(XMLNAMESPACES('http://x.y' AS "pgdefnamespace.pgsqlxml.internal"), '/x:rows/x:row' PASSING t1.doc COLUMNS data int PATH 'x:a[1][@hoge]') AS x; + +-- xpath and xpath_exists supports namespaces too +SELECT xpath('/x:rows/x:row/x:a[1][@hoge]', '50', ARRAY[ARRAY['x', 'http://x.y']]); +SELECT xpath('/rows/row/a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y']]); +SELECT xpath_exists('/x:rows/x:row/x:a[1][@hoge]', '50', ARRAY[ARRAY['x', 'http://x.y']]); +SELECT xpath_exists('/rows/row/a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y']]); + +-- should fail +SELECT xpath_exists('/rows/row/a[1][@hoge]', '50', ARRAY[ARRAY['', 'http://x.y'], ARRAY['', 'http://x.z']]);