diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index f6bead6..92259fd 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -10368,7 +10368,7 @@ SELECT xml_is_well_formed_document(' and + ' are left unchanged. This means that 'apos; + and 'quot; entities in XML input get expanded in xml + output columns. There is no way to prevent their expansion. If the path expression does not match for a given row but a DEFAULT expression is specified, the resulting default value is used. If no DEFAULT is given the - field will be NULL. + field will be NULL. Unlike normal queries, it is possible for + a DEFAULT expression to reference the value of output columns + that appear prior to it in the column-list, so the default of one + column may be based on the value of another column. @@ -10492,22 +10550,29 @@ SELECT * to null then the function terminates with an ERROR. + + + Unlike normal PostgreSQL functions, the PATH and + DEFAULT arguments to xmltable are not evaluated + to a simple value before calling the function. PATH + expressions are normally evaluated exactly once per input row + , and DEFAULT expressions each time a default is + needed for a field. If the expression qualifies as stable or immutable + the repeat evaluation may be skipped. Effectively xmltable + behaves more like a subquery than a function call. This means that you + can usefully use volatile functions like nextval in + DEFAULT expressions, your PATH expressions may + depend on other parts of the XML document, etc. + + + A column marked with the FOR ORDINALITY keyword will be populated with row numbers that match the order in which the the output rows appeared in the original input XML document. Only one column should be marked FOR ORDINALITY. - - - - - Empty tag is translated as empty string (possible attribute xsi:nil - has not any effect. The XPath expression in PATH clause is evaluated - for any input row. The expression in DEFAULT expression is evaluated for - any missing value (for any output row). - - + diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 7ce209d..4a345c4 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1838,6 +1838,10 @@ FigureColnameInternal(Node *node, char **name) *name = "xmlserialize"; return 2; case T_TableExpr: + /* + * Make TableExpr act like a regular function. Only + * XMLTABLE expr is supported in this moment. + */ *name = "xmltable"; return 2; default: diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 0f51275..5a3715c 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/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 3930452..7065415 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8285,6 +8285,11 @@ get_rule_expr(Node *node, deparse_context *context, { TableExpr *te = (TableExpr *) node; + /* + * Deparse TableExpr - the is only one TableExpr producer, + * the function XMLTABLE. + */ + /* c_expr shoud be closed in brackets */ appendStringInfoString(buf, "XMLTABLE("); diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index c931cee..aacbfe4 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; @@ -4102,310 +4102,6 @@ xml_is_well_formed_content(PG_FUNCTION_ARGS) #ifdef USE_LIBXML /* - * We need to work with XPath expression tokens. When expression - * starting or finishing with nodenames, then we can use prefix - * and suffix. When default namespace is defined, then we should to - * enhance any nodename and attribute without namespace by default - * namespace. The procession of XPath expression is linear. - */ - -typedef enum -{ - XPATH_TOKEN_NONE, - XPATH_TOKEN_NAME, - XPATH_TOKEN_STRING, - XPATH_TOKEN_NUMBER, - XPATH_TOKEN_OTHER -} XPathTokenType; - -typedef struct TokenInfo -{ - XPathTokenType ttype; - char *start; - int length; -} XPathTokenInfo; - -#define TOKEN_STACK_SIZE 10 - -typedef struct ParserData -{ - char *str; - char *cur; - XPathTokenInfo stack[TOKEN_STACK_SIZE]; - int stack_length; -} XPathParserData; - -/* Any high-bit-set character is OK (might be part of a multibyte char) */ -#define NODENAME_FIRSTCHAR(c) ((c) == '_' || (c) == '-' || \ - ((c) >= 'A' && (c) <= 'Z') || \ - ((c) >= 'a' && (c) <= 'z') || \ - (IS_HIGHBIT_SET(c))) - -#define IS_NODENAME_CHAR(c) (NODENAME_FIRSTCHAR(c) || (c) == '.' || \ - ((c) >= '0' && (c) <= '9')) - - -/* - * Returns next char after last char of token - */ -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 (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->stack_length = 0; -} - -/* - * Returns token from stack or read token - */ -static void -nextXPathToken(XPathParserData * parser, XPathTokenInfo * ti) -{ - if (parser->stack_length > 0) - memcpy(ti, &parser->stack[--parser->stack_length], - sizeof(XPathTokenInfo)); - else - parser->cur = getXPathToken(parser->cur, ti); -} - -/* - * Push token to stack - */ -static void -pushXPathToken(XPathParserData * parser, XPathTokenInfo * ti) -{ - if (parser->stack_length == TOKEN_STACK_SIZE) - elog(ERROR, "internal error"); - memcpy(&parser->stack[parser->stack_length++], ti, - sizeof(XPathTokenInfo)); -} - -/* - * 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); -} - -/* - * Working horse for XPath transformation. When XPath starting by node name, - * then prefix have to be applied. Any unqualified node name should be - * qualified by default namespace. inside_predicate is true, when - * _transformXPath is recursivly called because the predicate expression - * was found. - */ -static void -_transformXPath(StringInfo str, XPathParserData * parser, - bool inside_predicate, - char *prefix, char *def_namespace_name) -{ - XPathTokenInfo t1, - t2; - bool is_first_token = true; - bool last_token_is_name = false; - - nextXPathToken(parser, &t1); - - while (t1.ttype != XPATH_TOKEN_NONE) - { - switch (t1.ttype) - { - case XPATH_TOKEN_NUMBER: - case XPATH_TOKEN_STRING: - last_token_is_name = false; - is_first_token = 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; - } - } - - last_token_is_name = true; - nextXPathToken(parser, &t2); - if (t2.ttype == XPATH_TOKEN_OTHER) - { - if (*t2.start == '(') - last_token_is_name = false; - else if (*t2.start == ':') - is_qual_name = true; - } - - if (is_first_token && last_token_is_name && prefix != NULL) - appendStringInfoString(str, prefix); - - if (last_token_is_name && !is_qual_name && def_namespace_name != NULL) - appendStringInfo(str, "%s:", def_namespace_name); - - writeXPathToken(str, &t1); - is_first_token = false; - - 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; - - is_first_token = false; - - writeXPathToken(str, &t1); - - if (c == '[') - _transformXPath(str, parser, true, NULL, def_namespace_name); - else - { - last_token_is_name = false; - - if (c == ']' && inside_predicate) - return; - - else if (c == '@') - { - nextXPathToken(parser, &t1); - if (t1.ttype == XPATH_TOKEN_NAME) - { - bool is_qual_name = false; - - nextXPathToken(parser, &t2); - if (t2.ttype == XPATH_TOKEN_OTHER && *t2.start == ':') - is_qual_name = true; - - if (!is_qual_name && def_namespace_name != NULL) - appendStringInfo(str, "%s:", def_namespace_name); - - 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); - } - else - pushXPathToken(parser, &t1); - } - } - nextXPathToken(parser, &t1); - } - break; - - case XPATH_TOKEN_NONE: - elog(ERROR, "should not be here"); - } - } -} - -static void -transformXPath(StringInfo str, char *xpath, - char *prefix, char *def_namespace_name) -{ - XPathParserData parser; - - initStringInfo(str); - initXPathParser(&parser, xpath); - _transformXPath(str, &parser, false, prefix, def_namespace_name); -} - -/* * Functions for XmlTableBuilder * */ @@ -4840,7 +4536,8 @@ XmlTableGetValue(void *tcontext, int colnum, bool *isnull) { PG_TRY(); { - cstr = escape_xml((char *) str); + /* Copy string to PostgreSQL controlled memory */ + cstr = pstrdup((char *) str); } PG_CATCH(); { @@ -4848,14 +4545,14 @@ XmlTableGetValue(void *tcontext, int colnum, bool *isnull) PG_RE_THROW(); } PG_END_TRY(); + + xmlFree(str); } else { /* Return empty string when tag is empty */ cstr = pstrdup(""); } - - xmlFree(str); } else { diff --git a/src/backend/utils/adt/xpath_parser.c b/src/backend/utils/adt/xpath_parser.c new file mode 100644 index 0000000..a38798b --- /dev/null +++ b/src/backend/utils/adt/xpath_parser.c @@ -0,0 +1,340 @@ +/*------------------------------------------------------------------------- + * + * 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 + * + *------------------------------------------------------------------------- + */ + +/* + * All PostgreSQL xml related functionality is based on libxml2 library. + * XPath support is not a exception. But libxml2 doesn't provide all functions + * necessary for implementation of XMLTABLE function. There is not API for + * access to XPath expression AST (abstract syntax tree), and there is not + * support for default namespaces in XPath expressions. + * + * These requests are implemented with simple XPath parser/preprocessor. + * This XPath parser transform XPath expression to another XPath expression + * used in libxml2 XPath evaluation. It doesn't replace libxml2 XPath parser + * or libxml2 XPath expression evaluation. Currently libxml2 is stable, but + * without any movement in implementation new or missing features. + */ +#include "postgres.h" +#include "utils/xpath_parser.h" + +/* + * support functions for XMLTABLE function + * + */ +#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 TokenInfo +{ + XPathTokenType ttype; + char *start; + int length; +} XPathTokenInfo; + +#define TOKEN_STACK_SIZE 10 + +typedef struct ParserData +{ + char *str; + char *cur; + XPathTokenInfo stack[TOKEN_STACK_SIZE]; + int stack_length; +} XPathParserData; + +/* Any high-bit-set character is OK (might be part of a multibyte char) */ +#define NODENAME_FIRSTCHAR(c) ((c) == '_' || (c) == '-' || \ + ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + (IS_HIGHBIT_SET(c))) + +#define IS_NODENAME_CHAR(c) (NODENAME_FIRSTCHAR(c) || (c) == '.' || \ + ((c) >= '0' && (c) <= '9')) + + +/* + * Returns next char after last char of token + */ +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 (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->stack_length = 0; +} + +/* + * Returns token from stack or read token + */ +static void +nextXPathToken(XPathParserData * parser, XPathTokenInfo * ti) +{ + if (parser->stack_length > 0) + memcpy(ti, &parser->stack[--parser->stack_length], + sizeof(XPathTokenInfo)); + else + parser->cur = getXPathToken(parser->cur, ti); +} + +/* + * Push token to stack + */ +static void +pushXPathToken(XPathParserData * parser, XPathTokenInfo * ti) +{ + if (parser->stack_length == TOKEN_STACK_SIZE) + elog(ERROR, "internal error"); + memcpy(&parser->stack[parser->stack_length++], ti, + sizeof(XPathTokenInfo)); +} + +/* + * 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); +} + +/* + * Working horse for XPath transformation. When XPath starting by node name, + * then prefix have to be applied. Any unqualified node name should be + * qualified by default namespace. inside_predicate is true, when + * _transformXPath is recursivly called because the predicate expression + * was found. + */ +static void +_transformXPath(StringInfo str, XPathParserData * parser, + bool inside_predicate, + char *prefix, char *def_namespace_name) +{ + XPathTokenInfo t1, + t2; + bool is_first_token = true; + bool last_token_is_name = false; + + nextXPathToken(parser, &t1); + + while (t1.ttype != XPATH_TOKEN_NONE) + { + switch (t1.ttype) + { + case XPATH_TOKEN_NUMBER: + case XPATH_TOKEN_STRING: + last_token_is_name = false; + is_first_token = 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; + } + } + + last_token_is_name = true; + nextXPathToken(parser, &t2); + if (t2.ttype == XPATH_TOKEN_OTHER) + { + if (*t2.start == '(') + last_token_is_name = false; + else if (*t2.start == ':') + is_qual_name = true; + } + + if (is_first_token && last_token_is_name && prefix != NULL) + appendStringInfoString(str, prefix); + + if (last_token_is_name && !is_qual_name && def_namespace_name != NULL) + appendStringInfo(str, "%s:", def_namespace_name); + + writeXPathToken(str, &t1); + is_first_token = false; + + 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; + + is_first_token = false; + + writeXPathToken(str, &t1); + + if (c == '[') + _transformXPath(str, parser, true, NULL, def_namespace_name); + else + { + last_token_is_name = false; + + if (c == ']' && inside_predicate) + return; + + else if (c == '@') + { + nextXPathToken(parser, &t1); + if (t1.ttype == XPATH_TOKEN_NAME) + { + bool is_qual_name = false; + + nextXPathToken(parser, &t2); + if (t2.ttype == XPATH_TOKEN_OTHER && *t2.start == ':') + is_qual_name = true; + + if (!is_qual_name && def_namespace_name != NULL) + appendStringInfo(str, "%s:", def_namespace_name); + + 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); + } + else + pushXPathToken(parser, &t1); + } + } + nextXPathToken(parser, &t1); + } + break; + + case XPATH_TOKEN_NONE: + elog(ERROR, "should not be here"); + } + } +} + +void +transformXPath(StringInfo str, char *xpath, + char *prefix, char *def_namespace_name) +{ + XPathParserData parser; + + initStringInfo(str); + initXPathParser(&parser, xpath); + _transformXPath(str, &parser, false, prefix, def_namespace_name); +} + +#endif diff --git a/src/include/utils/xpath_parser.h b/src/include/utils/xpath_parser.h new file mode 100644 index 0000000..c6fc532 --- /dev/null +++ b/src/include/utils/xpath_parser.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * xpath_parser.h + * Declarations for XML XPath transformation. + * + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/xml.h + * + *------------------------------------------------------------------------- + */ + +#ifndef XPATH_PARSER_H +#define XPATH_PARSER_H + +#include "postgres.h" +#include "lib/stringinfo.h" + +void transformXPath(StringInfo str, char *xpath, + char *prefix, char *def_namespace_name); + +#endif /* XPATH_PARSER_H */ diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index 8ea477f..c8ac437 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -1266,14 +1266,43 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" 5 | Japan | 3 | JPJapan3Sinzo Abe (2 rows) -SELECT * FROM xmltable('/root' passing 'a1aa2abbbbxxxcccc' COLUMNS element text); - element ----------------- - a1aa2abbbbcccc +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text); + element +------------------- + a1aa2a bbbbcccc (1 row) -SELECT * FROM xmltable('/root' passing 'a1aa2abbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail ERROR: more than one value returned by column XPath expression +-- CDATA test +select * from xmltable('r' passing ' &"<>!foo]]>2' columns c text); + c +------------------------- + &"<>!foo + 2 +(2 rows) + +-- XML builtin entities +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent text); + ent +----- + ' + " + & + < + > +(5 rows) + +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent xml); + ent +------------------ + ' + " + & + < + > +(5 rows) + EXPLAIN (VERBOSE, COSTS OFF) SELECT xmltable.* FROM (SELECT data FROM xmldata) x, diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index a185ef7..83909b9 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -1058,18 +1058,38 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" ----+--------------+-----------+--------- (0 rows) -SELECT * FROM xmltable('/root' passing 'a1aa2abbbbxxxcccc' COLUMNS element text); +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text); ERROR: unsupported XML feature LINE 1: SELECT * FROM xmltable('/root' passing 'a1aa1aa2abbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail ERROR: unsupported XML feature LINE 1: SELECT * FROM xmltable('/root' passing 'a1a &"<>!foo]]>2' columns c text); +ERROR: unsupported XML feature +LINE 1: select * from xmltable('r' passing ''"&<>' COLUMNS ent text); +ERROR: unsupported XML feature +LINE 1: SELECT * FROM xmltable('/x/a' PASSING '''"&<>' COLUMNS ent xml); +ERROR: unsupported XML feature +LINE 1: SELECT * FROM xmltable('/x/a' PASSING ''JPJapan3Sinzo Abe (2 rows) -SELECT * FROM xmltable('/root' passing 'a1aa2abbbbxxxcccc' COLUMNS element text); - element ----------------- - a1aa2abbbbcccc +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text); + element +------------------- + a1aa2a bbbbcccc (1 row) -SELECT * FROM xmltable('/root' passing 'a1aa2abbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail ERROR: more than one value returned by column XPath expression +-- CDATA test +select * from xmltable('r' passing ' &"<>!foo]]>2' columns c text); + c +------------------------- + &"<>!foo + 2 +(2 rows) + +-- XML builtin entities +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent text); + ent +----- + ' + " + & + < + > +(5 rows) + +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent xml); + ent +------------------ + ' + " + & + < + > +(5 rows) + EXPLAIN (VERBOSE, COSTS OFF) SELECT xmltable.* FROM (SELECT data FROM xmldata) x, diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index b67af31..49f6bc4 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -393,8 +393,15 @@ SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH '.'); SELECT xmltable.* FROM xmldata, LATERAL xmltable('/ROWS/ROW[COUNTRY_NAME="Japan" or COUNTRY_NAME="India"]' PASSING data COLUMNS id int PATH '@id', "COUNTRY_NAME" text, "REGION_ID" int, rawdata xml PATH './*'); -SELECT * FROM xmltable('/root' passing 'a1aa2abbbbxxxcccc' COLUMNS element text); -SELECT * FROM xmltable('/root' passing 'a1aa2abbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text); +SELECT * FROM xmltable('/root' passing 'a1aa2a bbbbxxxcccc' COLUMNS element text PATH 'element/text()'); -- should fail + +-- CDATA test +select * from xmltable('r' passing ' &"<>!foo]]>2' columns c text); + +-- XML builtin entities +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent text); +SELECT * FROM xmltable('/x/a' PASSING ''"&<>' COLUMNS ent xml); EXPLAIN (VERBOSE, COSTS OFF) SELECT xmltable.*