diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 6b72c02781..fd95672dee 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.211 2007/02/02 00:07:03 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.212 2007/02/03 14:06:53 petere Exp $ * *------------------------------------------------------------------------- */ @@ -2834,11 +2834,10 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, { ExprState *e; text *data; - bool is_document; bool preserve_whitespace; - /* arguments are known to be text, bool, bool */ - Assert(list_length(xmlExpr->args) == 3); + /* arguments are known to be text, bool */ + Assert(list_length(xmlExpr->args) == 2); e = (ExprState *) linitial(xmlExpr->args); value = ExecEvalExpr(e, econtext, &isnull, NULL); @@ -2848,12 +2847,6 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, e = (ExprState *) lsecond(xmlExpr->args); value = ExecEvalExpr(e, econtext, &isnull, NULL); - if (isnull) /* probably can't happen */ - return (Datum) 0; - is_document = DatumGetBool(value); - - e = (ExprState *) lthird(xmlExpr->args); - value = ExecEvalExpr(e, econtext, &isnull, NULL); if (isnull) /* probably can't happen */ return (Datum) 0; preserve_whitespace = DatumGetBool(value); @@ -2861,7 +2854,7 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, *isNull = false; return PointerGetDatum(xmlparse(data, - is_document, + xexpr->xmloption, preserve_whitespace)); } break; @@ -2900,7 +2893,7 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, text *version; int standalone; - /* arguments are known to be xml, text, bool */ + /* arguments are known to be xml, text, int */ Assert(list_length(xmlExpr->args) == 3); e = (ExprState *) linitial(xmlExpr->args); @@ -2928,6 +2921,24 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, } break; + case IS_XMLSERIALIZE: + { + ExprState *e; + + /* argument type is known to be xml */ + Assert(list_length(xmlExpr->args) == 1); + + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + return (Datum) 0; + + *isNull = false; + + return PointerGetDatum(xmltotext_with_xmloption(DatumGetXmlP(value), xexpr->xmloption)); + } + break; + case IS_DOCUMENT: { ExprState *e; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index f213f216de..2d38d7fd60 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.364 2007/01/23 05:07:17 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.365 2007/02/03 14:06:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -1116,6 +1116,9 @@ _copyXmlExpr(XmlExpr *from) COPY_NODE_FIELD(named_args); COPY_NODE_FIELD(arg_names); COPY_NODE_FIELD(args); + COPY_SCALAR_FIELD(xmloption); + COPY_SCALAR_FIELD(type); + COPY_SCALAR_FIELD(typmod); return newnode; } @@ -1723,6 +1726,18 @@ _copyLockingClause(LockingClause *from) return newnode; } +static XmlSerialize * +_copyXmlSerialize(XmlSerialize *from) +{ + XmlSerialize *newnode = makeNode(XmlSerialize); + + COPY_SCALAR_FIELD(xmloption); + COPY_NODE_FIELD(expr); + COPY_NODE_FIELD(typename); + + return newnode; +} + static Query * _copyQuery(Query *from) { @@ -3430,6 +3445,9 @@ copyObject(void *from) case T_FuncWithArgs: retval = _copyFuncWithArgs(from); break; + case T_XmlSerialize: + retval = _copyXmlSerialize(from); + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from)); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 31754a7bc0..c17a40bbbb 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.297 2007/01/23 05:07:17 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.298 2007/02/03 14:06:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -462,6 +462,9 @@ _equalXmlExpr(XmlExpr *a, XmlExpr *b) COMPARE_NODE_FIELD(named_args); COMPARE_NODE_FIELD(arg_names); COMPARE_NODE_FIELD(args); + COMPARE_SCALAR_FIELD(xmloption); + COMPARE_SCALAR_FIELD(type); + COMPARE_SCALAR_FIELD(typmod); return true; } @@ -1830,6 +1833,15 @@ _equalFkConstraint(FkConstraint *a, FkConstraint *b) return true; } +static bool +_equalXmlSerialize(XmlSerialize *a, XmlSerialize *b) +{ + COMPARE_SCALAR_FIELD(xmloption); + COMPARE_NODE_FIELD(expr); + COMPARE_NODE_FIELD(typename); + + return true; +} /* * Stuff from pg_list.h @@ -2411,6 +2423,9 @@ equal(void *a, void *b) case T_FuncWithArgs: retval = _equalFuncWithArgs(a, b); break; + case T_XmlSerialize: + retval = _equalXmlSerialize(a, b); + break; default: elog(ERROR, "unrecognized node type: %d", diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index b79b7d1a2d..939c21f45a 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.295 2007/01/22 20:00:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.296 2007/02/03 14:06:54 petere Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -933,6 +933,9 @@ _outXmlExpr(StringInfo str, XmlExpr *node) WRITE_NODE_FIELD(named_args); WRITE_NODE_FIELD(arg_names); WRITE_NODE_FIELD(args); + WRITE_ENUM_FIELD(xmloption, XmlOptionType); + WRITE_OID_FIELD(type); + WRITE_INT_FIELD(typmod); } static void @@ -1521,6 +1524,16 @@ _outLockingClause(StringInfo str, LockingClause *node) WRITE_BOOL_FIELD(noWait); } +static void +_outXmlSerialize(StringInfo str, XmlSerialize *node) +{ + WRITE_NODE_TYPE("XMLSERIALIZE"); + + WRITE_ENUM_FIELD(xmloption, XmlOptionType); + WRITE_NODE_FIELD(expr); + WRITE_NODE_FIELD(typename); +} + static void _outColumnDef(StringInfo str, ColumnDef *node) { @@ -2290,6 +2303,9 @@ _outNode(StringInfo str, void *obj) case T_LockingClause: _outLockingClause(str, obj); break; + case T_XmlSerialize: + _outXmlSerialize(str, obj); + break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index b90c8dd713..17d36b4efe 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.201 2007/01/09 02:14:12 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.202 2007/02/03 14:06:54 petere Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -723,6 +723,9 @@ _readXmlExpr(void) READ_NODE_FIELD(named_args); READ_NODE_FIELD(arg_names); READ_NODE_FIELD(args); + READ_ENUM_FIELD(xmloption, XmlOptionType); + READ_OID_FIELD(type); + READ_INT_FIELD(typmod); READ_DONE(); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 1f1dfdb761..cf25c5607a 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.578 2007/02/01 19:10:27 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.579 2007/02/03 14:06:54 petere Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -350,7 +350,8 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args) %type xml_attribute_el %type xml_attribute_list xml_attributes %type xml_root_version opt_xml_root_standalone -%type document_or_content xml_whitespace_option +%type document_or_content +%type xml_whitespace_option /* @@ -1117,7 +1118,7 @@ set_rest: var_name TO var_list_or_default { VariableSetStmt *n = makeNode(VariableSetStmt); n->name = "xmloption"; - n->args = list_make1(makeStringConst($3 ? "DOCUMENT" : "CONTENT", NULL)); + n->args = list_make1(makeStringConst($3 == XMLOPTION_DOCUMENT ? "DOCUMENT" : "CONTENT", NULL)); $$ = n; } ; @@ -7903,10 +7904,11 @@ func_expr: func_name '(' ')' } | XMLPARSE '(' document_or_content a_expr xml_whitespace_option ')' { - $$ = makeXmlExpr(IS_XMLPARSE, NULL, NIL, - list_make3($4, - makeBoolAConst($3), - makeBoolAConst($5))); + XmlExpr *x = (XmlExpr *) makeXmlExpr(IS_XMLPARSE, NULL, NIL, + list_make2($4, + makeBoolAConst($5))); + x->xmloption = $3; + $$ = (Node *)x; } | XMLPI '(' NAME_P ColLabel ')' { @@ -7921,14 +7923,13 @@ func_expr: func_name '(' ')' $$ = makeXmlExpr(IS_XMLROOT, NULL, NIL, list_make3($3, $5, $6)); } - | XMLSERIALIZE '(' document_or_content a_expr AS Typename ')' + | XMLSERIALIZE '(' document_or_content a_expr AS SimpleTypename ')' { - /* - * FIXME: This should be made distinguishable from - * CAST (for reverse compilation at least). Also, - * what about the document/content option?? - */ - $$ = makeTypeCast($4, $6); + XmlSerialize *n = makeNode(XmlSerialize); + n->xmloption = $3; + n->expr = $4; + n->typename = $6; + $$ = (Node *)n; } ; @@ -7980,17 +7981,13 @@ xml_attribute_el: a_expr AS ColLabel } ; -document_or_content: DOCUMENT_P { $$ = TRUE; } - | CONTENT_P { $$ = FALSE; } +document_or_content: DOCUMENT_P { $$ = XMLOPTION_DOCUMENT; } + | CONTENT_P { $$ = XMLOPTION_CONTENT; } ; -/* - * XXX per SQL spec, the default should be STRIP WHITESPACE, but since we - * haven't implemented that yet, temporarily default to PRESERVE. - */ xml_whitespace_option: PRESERVE WHITESPACE_P { $$ = TRUE; } | STRIP_P WHITESPACE_P { $$ = FALSE; } - | /*EMPTY*/ { $$ = TRUE; } + | /*EMPTY*/ { $$ = FALSE; } ; /* diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index a807ef12de..e7a37a2cfa 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.209 2007/01/25 11:53:51 petere Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.210 2007/02/03 14:06:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -57,6 +57,7 @@ static Node *transformRowExpr(ParseState *pstate, RowExpr *r); static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c); static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m); static Node *transformXmlExpr(ParseState *pstate, XmlExpr *x); +static Node *transformXmlSerialize(ParseState *pstate, XmlSerialize *xs); static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b); static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref); static Node *transformWholeRowRef(ParseState *pstate, char *schemaname, @@ -224,6 +225,10 @@ transformExpr(ParseState *pstate, Node *expr) result = transformXmlExpr(pstate, (XmlExpr *) expr); break; + case T_XmlSerialize: + result = transformXmlSerialize(pstate, (XmlSerialize *) expr); + break; + case T_NullTest: { NullTest *n = (NullTest *) expr; @@ -1424,6 +1429,8 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x) newx->arg_names = lappend(newx->arg_names, makeString(argname)); } + newx->xmloption = x->xmloption; + if (x->op == IS_XMLELEMENT) { foreach(lc, newx->arg_names) @@ -1484,6 +1491,9 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x) newe = coerce_to_specific_type(pstate, newe, INT4OID, "XMLROOT"); break; + case IS_XMLSERIALIZE: + /* not handled here */ + break; case IS_DOCUMENT: newe = coerce_to_specific_type(pstate, newe, XMLOID, "IS DOCUMENT"); @@ -1496,6 +1506,38 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x) return (Node *) newx; } +static Node * +transformXmlSerialize(ParseState *pstate, XmlSerialize *xs) +{ + Oid targetType; + int32 targetTypmod; + XmlExpr *xexpr; + + xexpr = makeNode(XmlExpr); + xexpr->op = IS_XMLSERIALIZE; + xexpr->args = list_make1(coerce_to_specific_type(pstate, + transformExpr(pstate, xs->expr), + XMLOID, + "XMLSERIALIZE")); + + targetType = typenameTypeId(pstate, xs->typename); + targetTypmod = typenameTypeMod(pstate, xs->typename, targetType); + + xexpr->xmloption = xs->xmloption; + /* We actually only need these to be able to parse back the expression. */ + xexpr->type = targetType; + xexpr->typmod = targetTypmod; + + /* + * The actual target type is determined this way. SQL allows char + * and varchar as target types. We allow anything that can be + * cast implicitly from text. This way, user-defined text-like + * data types automatically fit in. + */ + return (Node *) coerce_to_target_type(pstate, (Node *) xexpr, TEXTOID, targetType, targetTypmod, + COERCION_IMPLICIT, COERCE_IMPLICIT_CAST); +} + static Node * transformBooleanTest(ParseState *pstate, BooleanTest *b) { @@ -1789,6 +1831,8 @@ exprType(Node *expr) case T_XmlExpr: if (((XmlExpr *) expr)->op == IS_DOCUMENT) type = BOOLOID; + else if (((XmlExpr *) expr)->op == IS_XMLSERIALIZE) + type = TEXTOID; else type = XMLOID; break; diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index dea29d1d8a..cc4bc091d2 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.153 2007/01/14 13:11:54 petere Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.154 2007/02/03 14:06:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -1337,11 +1337,17 @@ FigureColnameInternal(Node *node, char **name) case IS_XMLROOT: *name = "xmlroot"; return 2; + case IS_XMLSERIALIZE: + *name = "xmlserialize"; + return 2; case IS_DOCUMENT: /* nothing */ break; } break; + case T_XmlSerialize: + *name = "xmlserialize"; + return 2; default: break; } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 668266d1c4..3cd317361f 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.247 2007/01/30 02:39:27 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.248 2007/02/03 14:06:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -3856,9 +3856,19 @@ get_rule_expr(Node *node, deparse_context *context, case IS_XMLROOT: appendStringInfoString(buf, "XMLROOT("); break; + case IS_XMLSERIALIZE: + appendStringInfoString(buf, "XMLSERIALIZE("); + break; case IS_DOCUMENT: break; } + if (xexpr->op == IS_XMLPARSE || xexpr->op == IS_XMLSERIALIZE) + { + if (xexpr->xmloption == XMLOPTION_DOCUMENT) + appendStringInfoString(buf, "DOCUMENT "); + else + appendStringInfoString(buf, "CONTENT "); + } if (xexpr->name) { appendStringInfo(buf, "NAME %s", @@ -3899,24 +3909,17 @@ get_rule_expr(Node *node, deparse_context *context, case IS_XMLELEMENT: case IS_XMLFOREST: case IS_XMLPI: + case IS_XMLSERIALIZE: /* no extra decoration needed */ get_rule_expr((Node *) xexpr->args, context, true); break; case IS_XMLPARSE: - Assert(list_length(xexpr->args) == 3); - - con = (Const *) lsecond(xexpr->args); - Assert(IsA(con, Const)); - Assert(!con->constisnull); - if (DatumGetBool(con->constvalue)) - appendStringInfoString(buf, "DOCUMENT "); - else - appendStringInfoString(buf, "CONTENT "); + Assert(list_length(xexpr->args) == 2); get_rule_expr((Node *) linitial(xexpr->args), context, true); - con = (Const *) lthird(xexpr->args); + con = (Const *) lsecond(xexpr->args); Assert(IsA(con, Const)); Assert(!con->constisnull); if (DatumGetBool(con->constvalue)) @@ -3944,12 +3947,26 @@ get_rule_expr(Node *node, deparse_context *context, Assert(IsA(con, Const)); if (con->constisnull) /* suppress STANDALONE NO VALUE */ ; - else if (DatumGetBool(con->constvalue)) - appendStringInfoString(buf, - ", STANDALONE YES"); else - appendStringInfoString(buf, - ", STANDALONE NO"); + { + switch (DatumGetInt32(con->constvalue)) + { + case XML_STANDALONE_YES: + appendStringInfoString(buf, + ", STANDALONE YES"); + break; + case XML_STANDALONE_NO: + appendStringInfoString(buf, + ", STANDALONE NO"); + break; + case XML_STANDALONE_NO_VALUE: + appendStringInfoString(buf, + ", STANDALONE NO VALUE"); + break; + default: + break; + } + } break; case IS_DOCUMENT: get_rule_expr_paren((Node *) xexpr->args, context, false, node); @@ -3957,6 +3974,9 @@ get_rule_expr(Node *node, deparse_context *context, } } + if (xexpr->op == IS_XMLSERIALIZE) + appendStringInfo(buf, " AS %s", format_type_with_typemod(xexpr->type, + xexpr->typmod)); if (xexpr->op == IS_DOCUMENT) appendStringInfoString(buf, " IS DOCUMENT"); else diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 7d963148d0..70e566327d 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.24 2007/01/27 14:50:51 petere Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.25 2007/02/03 14:06:55 petere Exp $ * *------------------------------------------------------------------------- */ @@ -80,7 +80,7 @@ static void xml_ereport_by_code(int level, int sqlcode, static xmlChar *xml_text2xmlChar(text *in); static int parse_xml_decl(const xmlChar *str, size_t *lenp, xmlChar **version, xmlChar **encoding, int *standalone); static bool print_xml_decl(StringInfo buf, const xmlChar *version, pg_enc encoding, int standalone); -static xmlDocPtr xml_parse(text *data, bool is_document, bool preserve_whitespace, xmlChar *encoding); +static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, xmlChar *encoding); #endif /* USE_LIBXML */ @@ -112,7 +112,7 @@ xml_in(PG_FUNCTION_ARGS) * Parse the data to check if it is well-formed XML data. Assume * that ERROR occurred if parsing failed. */ - doc = xml_parse(vardata, (xmloption == XMLOPTION_DOCUMENT), true, NULL); + doc = xml_parse(vardata, xmloption, true, NULL); xmlFreeDoc(doc); PG_RETURN_XML_P(vardata); @@ -211,7 +211,7 @@ xml_recv(PG_FUNCTION_ARGS) * Parse the data to check if it is well-formed XML data. Assume * that ERROR occurred if parsing failed. */ - doc = xml_parse(result, (xmloption == XMLOPTION_DOCUMENT), true, encoding); + doc = xml_parse(result, xmloption, true, encoding); xmlFreeDoc(doc); newstr = (char *) pg_do_encoding_conversion((unsigned char *) str, @@ -435,7 +435,29 @@ texttoxml(PG_FUNCTION_ARGS) { text *data = PG_GETARG_TEXT_P(0); - PG_RETURN_XML_P(xmlparse(data, (xmloption == XMLOPTION_DOCUMENT), true)); + PG_RETURN_XML_P(xmlparse(data, xmloption, true)); +} + + +Datum +xmltotext(PG_FUNCTION_ARGS) +{ + xmltype *data = PG_GETARG_XML_P(0); + + PG_RETURN_TEXT_P(xmltotext_with_xmloption(data, xmloption)); +} + + +text * +xmltotext_with_xmloption(xmltype *data, XmlOptionType xmloption_arg) +{ + if (xmloption_arg == XMLOPTION_DOCUMENT && !xml_is_document(data)) + ereport(ERROR, + (errcode(ERRCODE_NOT_AN_XML_DOCUMENT), + errmsg("not an XML document"))); + + /* It's actually binary compatible, save for the above check. */ + return (text *) data; } @@ -499,12 +521,12 @@ xmlelement(XmlExprState *xmlExpr, ExprContext *econtext) xmltype * -xmlparse(text *data, bool is_document, bool preserve_whitespace) +xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace) { #ifdef USE_LIBXML xmlDocPtr doc; - doc = xml_parse(data, is_document, preserve_whitespace, NULL); + doc = xml_parse(data, xmloption_arg, preserve_whitespace, NULL); xmlFreeDoc(doc); return (xmltype *) data; @@ -723,7 +745,7 @@ xml_is_document(xmltype *arg) PG_TRY(); { - doc = xml_parse((text *) arg, true, true, NULL); + doc = xml_parse((text *) arg, XMLOPTION_DOCUMENT, true, NULL); result = true; } PG_CATCH(); @@ -996,7 +1018,7 @@ print_xml_decl(StringInfo buf, const xmlChar *version, pg_enc encoding, int stan * TODO maybe, libxml2's xmlreader is better? (do not construct DOM, yet do not use SAX - see xml_reader.c) */ static xmlDocPtr -xml_parse(text *data, bool is_document, bool preserve_whitespace, xmlChar *encoding) +xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, xmlChar *encoding) { int32 len; xmlChar *string; @@ -1024,7 +1046,7 @@ xml_parse(text *data, bool is_document, bool preserve_whitespace, xmlChar *encod xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, "could not allocate parser context"); - if (is_document) + if (xmloption_arg == XMLOPTION_DOCUMENT) { /* * Note, that here we try to apply DTD defaults diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 8157a3f6a5..2f1bb8ae1a 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.378 2007/01/31 19:33:54 neilc Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.379 2007/02/03 14:06:55 petere Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200701311 +#define CATALOG_VERSION_NO 200702031 #endif diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h index 4fbf237dad..0047f865af 100644 --- a/src/include/catalog/pg_cast.h +++ b/src/include/catalog/pg_cast.h @@ -10,7 +10,7 @@ * * Copyright (c) 2002-2007, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/catalog/pg_cast.h,v 1.30 2007/01/31 19:33:54 neilc Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_cast.h,v 1.31 2007/02/03 14:06:55 petere Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -300,7 +300,7 @@ DATA(insert ( 1266 25 939 i )); DATA(insert ( 25 1266 938 e )); DATA(insert ( 1700 25 1688 i )); DATA(insert ( 25 1700 1686 e )); -DATA(insert ( 142 25 0 e )); +DATA(insert ( 142 25 2922 e )); DATA(insert ( 25 142 2896 e )); /* @@ -340,7 +340,7 @@ DATA(insert ( 1266 1043 939 a )); DATA(insert ( 1043 1266 938 e )); DATA(insert ( 1700 1043 1688 a )); DATA(insert ( 1043 1700 1686 e )); -DATA(insert ( 142 1043 0 e )); +DATA(insert ( 142 1043 2922 e )); DATA(insert ( 1043 142 2896 e )); /* @@ -381,7 +381,7 @@ DATA(insert ( 1266 1042 939 a )); DATA(insert ( 1042 1266 938 e )); DATA(insert ( 1700 1042 1688 a )); DATA(insert ( 1042 1700 1686 e )); -DATA(insert ( 142 1042 0 e )); +DATA(insert ( 142 1042 2922 e )); /* * Length-coercion functions diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index eaee341096..500d239542 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.441 2007/01/28 16:16:52 neilc Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.442 2007/02/03 14:06:55 petere Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -4045,6 +4045,8 @@ DATA(insert OID = 2900 ( xmlconcat2 PGNSP PGUID 12 1 0 f f f f i 2 142 "1 DESCR("aggregate transition function"); DATA(insert OID = 2901 ( xmlagg PGNSP PGUID 12 1 0 t f f f i 1 142 "142" _null_ _null_ _null_ aggregate_dummy - _null_ )); DESCR("concatenate XML values"); +DATA(insert OID = 2922 ( text PGNSP PGUID 12 1 0 f f t f s 1 25 "142" _null_ _null_ _null_ xmltotext - _null_ )); +DESCR("serialize an XML value to a character string"); /* uuid */ DATA(insert OID = 2952 ( uuid_in PGNSP PGUID 12 1 0 f f t f i 1 2950 "2275" _null_ _null_ _null_ uuid_in - _null_ )); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index f3762facdd..2452f79294 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.193 2007/01/23 05:07:18 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.194 2007/02/03 14:06:55 petere Exp $ * *------------------------------------------------------------------------- */ @@ -331,6 +331,7 @@ typedef enum NodeTag T_FunctionParameter, T_LockingClause, T_RowMarkClause, + T_XmlSerialize, /* * TAGS FOR RANDOM OTHER STUFF diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index a252308bdb..0db7276302 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.339 2007/01/23 05:07:18 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.340 2007/02/03 14:06:55 petere Exp $ * *------------------------------------------------------------------------- */ @@ -485,6 +485,17 @@ typedef struct LockingClause bool noWait; /* NOWAIT option */ } LockingClause; +/* + * XMLSERIALIZE + */ +typedef struct XmlSerialize +{ + NodeTag type; + XmlOptionType xmloption; + Node *expr; + TypeName *typename; +} XmlSerialize; + /**************************************************************************** * Nodes for a Query tree diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index cea0cd2f6a..298ac0d95d 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.123 2007/01/14 13:11:54 petere Exp $ + * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.124 2007/02/03 14:06:56 petere Exp $ * *------------------------------------------------------------------------- */ @@ -726,9 +726,16 @@ typedef enum XmlExprOp IS_XMLPARSE, /* XMLPARSE(text, is_doc, preserve_ws) */ IS_XMLPI, /* XMLPI(name [, args]) */ IS_XMLROOT, /* XMLROOT(xml, version, standalone) */ + IS_XMLSERIALIZE, /* XMLSERIALIZE(is_document, xmlval) */ IS_DOCUMENT /* xmlval IS DOCUMENT */ } XmlExprOp; +typedef enum +{ + XMLOPTION_DOCUMENT, + XMLOPTION_CONTENT +} XmlOptionType; + typedef struct XmlExpr { Expr xpr; @@ -737,6 +744,9 @@ typedef struct XmlExpr List *named_args; /* non-XML expressions for xml_attributes */ List *arg_names; /* parallel list of Value strings */ List *args; /* list of expressions */ + XmlOptionType xmloption; /* DOCUMENT or CONTENT */ + Oid type; /* target type for XMLSERIALIZE */ + int32 typmod; } XmlExpr; /* diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h index 0e4d83a802..010918ab41 100644 --- a/src/include/utils/errcodes.h +++ b/src/include/utils/errcodes.h @@ -11,7 +11,7 @@ * * Copyright (c) 2003-2007, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.22 2007/01/05 22:19:59 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.23 2007/02/03 14:06:56 petere Exp $ * *------------------------------------------------------------------------- */ @@ -148,6 +148,7 @@ #define ERRCODE_INVALID_BINARY_REPRESENTATION MAKE_SQLSTATE('2','2', 'P','0','3') #define ERRCODE_BAD_COPY_FILE_FORMAT MAKE_SQLSTATE('2','2', 'P','0','4') #define ERRCODE_UNTRANSLATABLE_CHARACTER MAKE_SQLSTATE('2','2', 'P','0','5') +#define ERRCODE_NOT_AN_XML_DOCUMENT MAKE_SQLSTATE('2', '2', '0', '0', 'L') #define ERRCODE_INVALID_XML_DOCUMENT MAKE_SQLSTATE('2', '2', '0', '0', 'M') #define ERRCODE_INVALID_XML_CONTENT MAKE_SQLSTATE('2', '2', '0', '0', 'N') #define ERRCODE_INVALID_XML_COMMENT MAKE_SQLSTATE('2', '2', '0', '0', 'S') diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h index f5b33512cf..f207917ea8 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.13 2007/01/25 11:53:51 petere Exp $ + * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.14 2007/02/03 14:06:56 petere Exp $ * *------------------------------------------------------------------------- */ @@ -17,10 +17,12 @@ #include "fmgr.h" #include "nodes/execnodes.h" +#include "nodes/primnodes.h" typedef struct varlena xmltype; #define DatumGetXmlP(X) ((xmltype *) PG_DETOAST_DATUM(X)) +#define XmlPGetDatum(X) PointerGetDatum(X) #define PG_GETARG_XML_P(n) DatumGetXmlP(PG_GETARG_DATUM(n)) #define PG_RETURN_XML_P(x) PG_RETURN_POINTER(x) @@ -32,6 +34,7 @@ extern Datum xml_send(PG_FUNCTION_ARGS); extern Datum xmlcomment(PG_FUNCTION_ARGS); extern Datum xmlconcat2(PG_FUNCTION_ARGS); extern Datum texttoxml(PG_FUNCTION_ARGS); +extern Datum xmltotext(PG_FUNCTION_ARGS); extern Datum xmlvalidate(PG_FUNCTION_ARGS); typedef enum @@ -44,10 +47,11 @@ typedef enum extern xmltype *xmlconcat(List *args); extern xmltype *xmlelement(XmlExprState *xmlExpr, ExprContext *econtext); -extern xmltype *xmlparse(text *data, bool is_doc, bool preserve_whitespace); +extern xmltype *xmlparse(text *data, XmlOptionType xmloption, bool preserve_whitespace); extern xmltype *xmlpi(char *target, text *arg, bool arg_is_null, bool *result_is_null); extern xmltype *xmlroot(xmltype *data, text *version, int standalone); extern bool xml_is_document(xmltype *arg); +extern text *xmltotext_with_xmloption(xmltype *data, XmlOptionType xmloption_arg); extern char *map_sql_identifier_to_xml_name(char *ident, bool fully_escaped); extern char *map_xml_name_to_sql_identifier(char *name); @@ -61,12 +65,6 @@ typedef enum extern XmlBinaryType xmlbinary; -typedef enum -{ - XMLOPTION_DOCUMENT, - XMLOPTION_CONTENT -} XmlOptionType; - extern XmlOptionType xmloption; #endif /* XML_H */ diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 33bebcfa66..fcbfe3cad5 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -288,8 +288,6 @@ WHERE c.castfunc = p.oid AND -- those are binary-compatible while the reverse way goes through rtrim(). -- As of 8.2, this finds the cast from cidr to inet, because that is a -- trivial binary coercion while the other way goes through inet_to_cidr(). --- As of 8.3, this finds casts from xml to text, varchar, and bpchar, --- because the other direction has to go through xmlparse(). SELECT * FROM pg_cast c WHERE c.castfunc = 0 AND @@ -302,10 +300,7 @@ WHERE c.castfunc = 0 AND 25 | 1042 | 0 | i 1043 | 1042 | 0 | i 650 | 869 | 0 | i - 142 | 25 | 0 | e - 142 | 1043 | 0 | e - 142 | 1042 | 0 | e -(6 rows) +(3 rows) -- **************** pg_operator **************** -- Look for illegal values in pg_operator fields. diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index 0c08667706..6d79a4166e 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -275,13 +275,21 @@ SELECT xmlroot ( foo (1 row) -SELECT xmlserialize(content data as character varying) FROM xmltest; - data +SELECT xmlserialize(content data as character varying(20)) FROM xmltest; + xmlserialize -------------------- one two (2 rows) +SELECT xmlserialize(content 'good' as char(10)); + xmlserialize +-------------- + good +(1 row) + +SELECT xmlserialize(document 'bad' as text); +ERROR: not an XML document SELECT xml 'bar' IS DOCUMENT; ?column? ---------- @@ -368,3 +376,27 @@ EXECUTE foo ('good'); good (1 row) +-- Test backwards parsing +CREATE VIEW xmlview1 AS SELECT xmlcomment('test'); +CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you'); +CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&'); +CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp; +CREATE VIEW xmlview5 AS SELECT xmlparse(content 'x'); +CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar'); +CREATE VIEW xmlview7 AS SELECT xmlroot(xml '', version no value, standalone yes); +CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10)); +CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text); +SELECT table_name, view_definition FROM information_schema.views WHERE table_name LIKE 'xmlview%'; + table_name | view_definition +------------+-------------------------------------------------------------------------------------------------------------------------------- + xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment; + xmlview2 | SELECT XMLCONCAT('hello'::"xml", 'you'::"xml") AS "xmlconcat"; + xmlview3 | SELECT XMLELEMENT(NAME element, XMLATTRIBUTES(1 AS ":one:", 'deuce' AS two), 'content&') AS "xmlelement"; + xmlview4 | SELECT XMLELEMENT(NAME employee, XMLFOREST(emp."name" AS "name", emp.age AS age, emp.salary AS pay)) AS "xmlelement" FROM emp; + xmlview5 | SELECT XMLPARSE(CONTENT 'x'::text STRIP WHITESPACE) AS "xmlparse"; + xmlview6 | SELECT XMLPI(NAME foo, 'bar'::text) AS "xmlpi"; + xmlview7 | SELECT XMLROOT(''::"xml", VERSION NO VALUE, STANDALONE YES) AS "xmlroot"; + xmlview8 | SELECT (XMLSERIALIZE(CONTENT 'good'::"xml" AS character(10)))::character(10) AS "xmlserialize"; + xmlview9 | SELECT XMLSERIALIZE(CONTENT 'good'::"xml" AS text) AS "xmlserialize"; +(9 rows) + diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index 89124ebb98..899ed4cd26 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -122,11 +122,15 @@ SELECT xmlroot ( standalone yes ); ERROR: no XML support in this installation -SELECT xmlserialize(content data as character varying) FROM xmltest; - data ------- +SELECT xmlserialize(content data as character varying(20)) FROM xmltest; + xmlserialize +-------------- (0 rows) +SELECT xmlserialize(content 'good' as char(10)); +ERROR: no XML support in this installation +SELECT xmlserialize(document 'bad' as text); +ERROR: no XML support in this installation SELECT xml 'bar' IS DOCUMENT; ERROR: no XML support in this installation SELECT xml 'barfoo' IS DOCUMENT; @@ -168,3 +172,27 @@ EXECUTE foo (''); ERROR: prepared statement "foo" does not exist EXECUTE foo ('good'); ERROR: prepared statement "foo" does not exist +-- Test backwards parsing +CREATE VIEW xmlview1 AS SELECT xmlcomment('test'); +CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you'); +ERROR: no XML support in this installation +CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&'); +ERROR: no XML support in this installation +CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp; +ERROR: no XML support in this installation +CREATE VIEW xmlview5 AS SELECT xmlparse(content 'x'); +CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar'); +ERROR: no XML support in this installation +CREATE VIEW xmlview7 AS SELECT xmlroot(xml '', version no value, standalone yes); +ERROR: no XML support in this installation +CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10)); +ERROR: no XML support in this installation +CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text); +ERROR: no XML support in this installation +SELECT table_name, view_definition FROM information_schema.views WHERE table_name LIKE 'xmlview%'; + table_name | view_definition +------------+------------------------------------------------------------------------------- + xmlview1 | SELECT xmlcomment('test'::text) AS xmlcomment; + xmlview5 | SELECT XMLPARSE(CONTENT 'x'::text STRIP WHITESPACE) AS "xmlparse"; +(2 rows) + diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 7eff2195df..cbf9baf672 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -238,9 +238,6 @@ WHERE c.castfunc = p.oid AND -- As of 8.2, this finds the cast from cidr to inet, because that is a -- trivial binary coercion while the other way goes through inet_to_cidr(). --- As of 8.3, this finds casts from xml to text, varchar, and bpchar, --- because the other direction has to go through xmlparse(). - SELECT * FROM pg_cast c WHERE c.castfunc = 0 AND diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index b3117c2424..2f919fb42a 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -68,6 +68,7 @@ SELECT xmlpi(name foo, null); SELECT xmlpi(name xmlstuff, null); SELECT xmlpi(name foo, ' bar'); + SELECT xmlroot(xml '', version no value, standalone no value); SELECT xmlroot(xml '', version '2.0'); SELECT xmlroot(xml '', version no value, standalone yes); @@ -95,7 +96,9 @@ SELECT xmlroot ( ); -SELECT xmlserialize(content data as character varying) FROM xmltest; +SELECT xmlserialize(content data as character varying(20)) FROM xmltest; +SELECT xmlserialize(content 'good' as char(10)); +SELECT xmlserialize(document 'bad' as text); SELECT xml 'bar' IS DOCUMENT; @@ -125,3 +128,18 @@ EXECUTE foo ('bad'); SET XML OPTION CONTENT; EXECUTE foo (''); EXECUTE foo ('good'); + + +-- Test backwards parsing + +CREATE VIEW xmlview1 AS SELECT xmlcomment('test'); +CREATE VIEW xmlview2 AS SELECT xmlconcat('hello', 'you'); +CREATE VIEW xmlview3 AS SELECT xmlelement(name element, xmlattributes (1 as ":one:", 'deuce' as two), 'content&'); +CREATE VIEW xmlview4 AS SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp; +CREATE VIEW xmlview5 AS SELECT xmlparse(content 'x'); +CREATE VIEW xmlview6 AS SELECT xmlpi(name foo, 'bar'); +CREATE VIEW xmlview7 AS SELECT xmlroot(xml '', version no value, standalone yes); +CREATE VIEW xmlview8 AS SELECT xmlserialize(content 'good' as char(10)); +CREATE VIEW xmlview9 AS SELECT xmlserialize(content 'good' as text); + +SELECT table_name, view_definition FROM information_schema.views WHERE table_name LIKE 'xmlview%';