From 8c1de5fb0010ae712568f1706b737270c3609bd8 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 21 Dec 2006 16:05:16 +0000 Subject: [PATCH] Initial SQL/XML support: xml data type and initial set of functions. --- configure | 267 +++++++ configure.in | 17 +- doc/src/sgml/datatype.sgml | 84 +- doc/src/sgml/func.sgml | 111 ++- doc/src/sgml/installation.sgml | 11 +- src/backend/executor/execQual.c | 177 ++++- src/backend/nodes/copyfuncs.c | 21 +- src/backend/nodes/equalfuncs.c | 16 +- src/backend/nodes/outfuncs.c | 16 +- src/backend/nodes/readfuncs.c | 20 +- src/backend/optimizer/util/clauses.c | 27 +- src/backend/parser/gram.y | 198 ++++- src/backend/parser/keywords.c | 19 +- src/backend/parser/parse_coerce.c | 42 +- src/backend/parser/parse_expr.c | 61 +- src/backend/parser/parse_target.c | 17 +- src/backend/utils/adt/Makefile | 4 +- src/backend/utils/adt/ruleutils.c | 27 +- src/backend/utils/adt/xml.c | 942 +++++++++++++++++++++++ src/backend/utils/mb/mbutils.c | 4 +- src/include/catalog/pg_cast.h | 7 +- src/include/catalog/pg_proc.h | 22 +- src/include/catalog/pg_type.h | 6 +- src/include/nodes/execnodes.h | 18 +- src/include/nodes/nodes.h | 4 +- src/include/nodes/primnodes.h | 22 +- src/include/parser/parse_coerce.h | 4 +- src/include/pg_config.h.in | 6 + src/include/utils/errcodes.h | 6 +- src/include/utils/xml.h | 37 + src/test/regress/expected/opr_sanity.out | 9 +- src/test/regress/expected/prepare.out | 2 +- src/test/regress/expected/rules.out | 44 +- src/test/regress/expected/xml.out | 145 ++++ src/test/regress/expected/xml_1.out | 77 ++ src/test/regress/parallel_schedule | 4 +- src/test/regress/serial_schedule | 3 +- src/test/regress/sql/opr_sanity.sql | 5 +- src/test/regress/sql/xml.sql | 72 ++ 39 files changed, 2446 insertions(+), 128 deletions(-) create mode 100644 src/backend/utils/adt/xml.c create mode 100644 src/include/utils/xml.h create mode 100644 src/test/regress/expected/xml.out create mode 100644 src/test/regress/expected/xml_1.out create mode 100644 src/test/regress/sql/xml.sql diff --git a/configure b/configure index dc96e1ca41..f85511bdef 100755 --- a/configure +++ b/configure @@ -894,6 +894,7 @@ Optional Packages: --with-openssl build with OpenSSL support --without-readline do not use GNU Readline nor BSD Libedit for editing --with-libedit-preferred prefer BSD Libedit over GNU Readline + --with-libxml build with XML support --without-zlib do not use Zlib --with-gnu-ld assume the C compiler uses GNU ld [default=no] @@ -4160,6 +4161,42 @@ fi; +# +# XML +# + +pgac_args="$pgac_args with_libxml" + + +# Check whether --with-libxml or --without-libxml was given. +if test "${with_libxml+set}" = set; then + withval="$with_libxml" + + case $withval in + yes) + +cat >>confdefs.h <<\_ACEOF +#define USE_LIBXML 1 +_ACEOF + + ;; + no) + : + ;; + *) + { { echo "$as_me:$LINENO: error: no argument expected for --with-libxml option" >&5 +echo "$as_me: error: no argument expected for --with-libxml option" >&2;} + { (exit 1); exit 1; }; } + ;; + esac + +else + with_libxml=no + +fi; + + + # # Zlib # @@ -7268,6 +7305,87 @@ fi fi +if test "$with_libxml" = yes ; then + +echo "$as_me:$LINENO: checking for xmlInitParser in -lxml2" >&5 +echo $ECHO_N "checking for xmlInitParser in -lxml2... $ECHO_C" >&6 +if test "${ac_cv_lib_xml2_xmlInitParser+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lxml2 $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any gcc2 internal prototype to avoid an error. */ +#ifdef __cplusplus +extern "C" +#endif +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char xmlInitParser (); +int +main () +{ +xmlInitParser (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 + (eval $ac_link) 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && + { ac_try='test -z "$ac_c_werror_flag" + || test ! -s conftest.err' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; } && + { ac_try='test -s conftest$ac_exeext' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + ac_cv_lib_xml2_xmlInitParser=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +ac_cv_lib_xml2_xmlInitParser=no +fi +rm -f conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +echo "$as_me:$LINENO: result: $ac_cv_lib_xml2_xmlInitParser" >&5 +echo "${ECHO_T}$ac_cv_lib_xml2_xmlInitParser" >&6 +if test $ac_cv_lib_xml2_xmlInitParser = yes; then + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBXML2 1 +_ACEOF + + LIBS="-lxml2 $LIBS" + +else + { { echo "$as_me:$LINENO: error: library 'xml2' is required for XML support" >&5 +echo "$as_me: error: library 'xml2' is required for XML support" >&2;} + { (exit 1); exit 1; }; } +fi + +fi + ## ## Header files @@ -10359,6 +10477,155 @@ fi done +fi + +if test "$with_libxml" = yes ; then + if test "${ac_cv_header_libxml_parser_h+set}" = set; then + echo "$as_me:$LINENO: checking for libxml/parser.h" >&5 +echo $ECHO_N "checking for libxml/parser.h... $ECHO_C" >&6 +if test "${ac_cv_header_libxml_parser_h+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +fi +echo "$as_me:$LINENO: result: $ac_cv_header_libxml_parser_h" >&5 +echo "${ECHO_T}$ac_cv_header_libxml_parser_h" >&6 +else + # Is the header compilable? +echo "$as_me:$LINENO: checking libxml/parser.h usability" >&5 +echo $ECHO_N "checking libxml/parser.h usability... $ECHO_C" >&6 +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +#include +_ACEOF +rm -f conftest.$ac_objext +if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && + { ac_try='test -z "$ac_c_werror_flag" + || test ! -s conftest.err' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; } && + { ac_try='test -s conftest.$ac_objext' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + ac_header_compiler=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +ac_header_compiler=no +fi +rm -f conftest.err conftest.$ac_objext conftest.$ac_ext +echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 +echo "${ECHO_T}$ac_header_compiler" >&6 + +# Is the header present? +echo "$as_me:$LINENO: checking libxml/parser.h presence" >&5 +echo $ECHO_N "checking libxml/parser.h presence... $ECHO_C" >&6 +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include +_ACEOF +if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5 + (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null; then + if test -s conftest.err; then + ac_cpp_err=$ac_c_preproc_warn_flag + ac_cpp_err=$ac_cpp_err$ac_c_werror_flag + else + ac_cpp_err= + fi +else + ac_cpp_err=yes +fi +if test -z "$ac_cpp_err"; then + ac_header_preproc=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_preproc=no +fi +rm -f conftest.err conftest.$ac_ext +echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 +echo "${ECHO_T}$ac_header_preproc" >&6 + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in + yes:no: ) + { echo "$as_me:$LINENO: WARNING: libxml/parser.h: accepted by the compiler, rejected by the preprocessor!" >&5 +echo "$as_me: WARNING: libxml/parser.h: accepted by the compiler, rejected by the preprocessor!" >&2;} + { echo "$as_me:$LINENO: WARNING: libxml/parser.h: proceeding with the compiler's result" >&5 +echo "$as_me: WARNING: libxml/parser.h: proceeding with the compiler's result" >&2;} + ac_header_preproc=yes + ;; + no:yes:* ) + { echo "$as_me:$LINENO: WARNING: libxml/parser.h: present but cannot be compiled" >&5 +echo "$as_me: WARNING: libxml/parser.h: present but cannot be compiled" >&2;} + { echo "$as_me:$LINENO: WARNING: libxml/parser.h: check for missing prerequisite headers?" >&5 +echo "$as_me: WARNING: libxml/parser.h: check for missing prerequisite headers?" >&2;} + { echo "$as_me:$LINENO: WARNING: libxml/parser.h: see the Autoconf documentation" >&5 +echo "$as_me: WARNING: libxml/parser.h: see the Autoconf documentation" >&2;} + { echo "$as_me:$LINENO: WARNING: libxml/parser.h: section \"Present But Cannot Be Compiled\"" >&5 +echo "$as_me: WARNING: libxml/parser.h: section \"Present But Cannot Be Compiled\"" >&2;} + { echo "$as_me:$LINENO: WARNING: libxml/parser.h: proceeding with the preprocessor's result" >&5 +echo "$as_me: WARNING: libxml/parser.h: proceeding with the preprocessor's result" >&2;} + { echo "$as_me:$LINENO: WARNING: libxml/parser.h: in the future, the compiler will take precedence" >&5 +echo "$as_me: WARNING: libxml/parser.h: in the future, the compiler will take precedence" >&2;} + ( + cat <<\_ASBOX +## ---------------------------------------- ## +## Report this to pgsql-bugs@postgresql.org ## +## ---------------------------------------- ## +_ASBOX + ) | + sed "s/^/$as_me: WARNING: /" >&2 + ;; +esac +echo "$as_me:$LINENO: checking for libxml/parser.h" >&5 +echo $ECHO_N "checking for libxml/parser.h... $ECHO_C" >&6 +if test "${ac_cv_header_libxml_parser_h+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + ac_cv_header_libxml_parser_h=$ac_header_preproc +fi +echo "$as_me:$LINENO: result: $ac_cv_header_libxml_parser_h" >&5 +echo "${ECHO_T}$ac_cv_header_libxml_parser_h" >&6 + +fi +if test $ac_cv_header_libxml_parser_h = yes; then + : +else + { { echo "$as_me:$LINENO: error: header file is required for XML support" >&5 +echo "$as_me: error: header file is required for XML support" >&2;} + { (exit 1); exit 1; }; } +fi + + fi if test "$with_ldap" = yes ; then diff --git a/configure.in b/configure.in index 096f806060..1391525acf 100644 --- a/configure.in +++ b/configure.in @@ -1,5 +1,5 @@ dnl Process this file with autoconf to produce a configure script. -dnl $PostgreSQL: pgsql/configure.in,v 1.492 2006/12/14 21:49:54 tgl Exp $ +dnl $PostgreSQL: pgsql/configure.in,v 1.493 2006/12/21 16:05:12 petere Exp $ dnl dnl Developers, please strive to achieve this order: dnl @@ -531,6 +531,13 @@ PGAC_ARG_BOOL(with, libedit-preferred, no, [ --with-libedit-preferred prefer BSD Libedit over GNU Readline]) +# +# XML +# +PGAC_ARG_BOOL(with, libxml, no, [ --with-libxml build with XML support], + [AC_DEFINE([USE_LIBXML], 1, [Define to 1 to build with XML support. (--with-libxml)])]) + + # # Zlib # @@ -716,6 +723,10 @@ if test "$with_pam" = yes ; then AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for PAM])]) fi +if test "$with_libxml" = yes ; then + AC_CHECK_LIB(xml2, xmlInitParser, [], [AC_MSG_ERROR([library 'xml2' is required for XML support])]) +fi + ## ## Header files @@ -791,6 +802,10 @@ if test "$with_pam" = yes ; then [AC_MSG_ERROR([header file or is required for PAM.])])]) fi +if test "$with_libxml" = yes ; then + AC_CHECK_HEADER(libxml/parser.h, [], [AC_MSG_ERROR([header file is required for XML support])]) +fi + if test "$with_ldap" = yes ; then if test "$PORTNAME" != "win32"; then AC_CHECK_HEADERS(ldap.h, [], diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 4bc950acf0..f68714bd20 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -1,4 +1,4 @@ - + Data Types @@ -233,6 +233,12 @@ timestamptz date and time, including time zone + + + xml + + XML data + @@ -248,7 +254,8 @@ precision, integer, interval, numeric, decimal, real, smallint, time (with or without time zone), - timestamp (with or without time zone). + timestamp (with or without time zone), + xml. @@ -3358,12 +3365,21 @@ SELECT * FROM pg_attribute - <acronym>XML</> Document Support + <acronym>XML</> Type - - xml + + XML + + The data type xml can be used to store XML data. Its + advantage over storing XML data in, say, a text field is that it + checks the input values for well-formedness, and there are support + functions to perform type-safe operations on it; see . Currently, there is no support for + validation against a specific XML schema. + + XML (Extensible Markup Language) support is not one capability, but a variety of features supported by a database @@ -3378,22 +3394,6 @@ SELECT * FROM pg_attribute - - - Storage - - - - PostgreSQL does not have a specialized XML data type. - Users should store XML documents in ordinary - TEXT fields. If you need the document split apart into - its component parts so each element is stored separately, you must - use a middle-ware solution to do that, but once done, the data - becomes relational and has to be processed accordingly. - - - - Import/Export @@ -3408,21 +3408,6 @@ SELECT * FROM pg_attribute - - Validation - - - - /contrib/xml2 has a function called - xml_is_well_formed() that can be used in a CHECK - constraint to enforce that a field contains well-formed XML. - It does not support validation against a specific XML - schema. A server-side language with XML capabilities - could be used to do schema-specific XML checks. - - - - Indexing @@ -3438,20 +3423,6 @@ SELECT * FROM pg_attribute - - Modification - - - - If an UPDATE does not modify an XML field, - the XML data is shared between the old and new rows. - However, if the UPDATE modifies an XML - field, a full modified copy of the XML field must be - created internally. - - - - Searching @@ -3487,19 +3458,6 @@ SELECT * FROM pg_attribute - - - Missing Features - - - - Missing features include XQuery, SQL/XML syntax (ISO/IEC - 9075-14), and an XML data type optimized for - XML storage. - - - - diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 1e092c4ce7..c2a3409645 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,4 +1,4 @@ - + Functions and Operators @@ -10741,4 +10741,113 @@ SELECT (pg_stat_file('filename')).modification; + + + XML Functions + + + The functions and function-like expressions described in this + section operate on values of type xml. + + + + <literal>xmlcomment</literal> + + + xmlcomment + + + + xmlcomment(text) + + + + Creates an XML comment. + + + + + <literal>xmlconcat</literal> + + + xmlconcat + + + + xmlconcat(xml, xml, ...) + + + + Combines a list of individual XML values to create a + single value containing an XML forest. + + + + + <literal>xmlelement</literal> + + + xmlelement + + + + xmlelement(name name, xmlattribytes(value AS label, ... ) + , content, ...) + + + + Creates an XML element, allowing the name to be specified. + + + + + <literal>xmlforest</literal> + + + xmlforest + + + + xmlforest(value AS label, ...) + + + + Creates XML elements from columns, using the name of each + column as the name of the corresponding element. + + + + + <literal>xmlpi</literal> + + + xmlpi + + + + xmlpi(name target , content) + + + + Creates an XML processing instruction. + + + + + <literal>xmlroot</literal> + + + xmlroot + + + + xmlroot(xml, version text , standalone yes|no|no value) + + + + Creates the root node of an XML document. + + + + diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml index 5d071586fc..a56ba9ad9b 100644 --- a/doc/src/sgml/installation.sgml +++ b/doc/src/sgml/installation.sgml @@ -1,4 +1,4 @@ - + <![%standalone-include[<productname>PostgreSQL</>]]> @@ -905,6 +905,15 @@ su - postgres </listitem> </varlistentry> + <varlistentry> + <term><option>--with-libxml</option></term> + <listitem> + <para> + Build with libxml, required for SQL/XML support. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--enable-integer-datetimes</option></term> <listitem> diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 1dbef5f15c..10b02b4a3e 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.199 2006/11/17 16:46:27 petere Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.200 2006/12/21 16:05:13 petere Exp $ * *------------------------------------------------------------------------- */ @@ -52,6 +52,7 @@ #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/typcache.h" +#include "utils/xml.h" /* static function decls */ @@ -119,6 +120,8 @@ static Datum ExecEvalMinMax(MinMaxExprState *minmaxExpr, static Datum ExecEvalNullIf(FuncExprState *nullIfExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalNullTest(NullTestState *nstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); @@ -2878,6 +2881,120 @@ ExecEvalBooleanTest(GenericExprState *bstate, } } +/* ---------------------------------------------------------------- + * ExecEvalXml + * ---------------------------------------------------------------- + */ + +static Datum +ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + StringInfoData buf; + bool isnull; + ListCell *arg; + text *result = NULL; + int len; + + initStringInfo(&buf); + + *isNull = false; + + if (isDone) + *isDone = ExprSingleResult; + + switch (xmlExpr->op) + { + case IS_XMLCONCAT: + *isNull = true; + + foreach(arg, xmlExpr->args) + { + ExprState *e = (ExprState *) lfirst(arg); + Datum value = ExecEvalExpr(e, econtext, &isnull, NULL); + + if (!isnull) + { + appendStringInfoString(&buf, DatumGetCString(OidFunctionCall1(xmlExpr->arg_typeout, value))); + *isNull = false; + } + } + break; + + case IS_XMLELEMENT: + { + int state = 0, i = 0; + appendStringInfo(&buf, "<%s", xmlExpr->name); + foreach(arg, xmlExpr->named_args) + { + GenericExprState *gstate = (GenericExprState *) lfirst(arg); + Datum value = ExecEvalExpr(gstate->arg, econtext, &isnull, NULL); + if (!isnull) + { + char *outstr = DatumGetCString(OidFunctionCall1(xmlExpr->named_args_tcache[i], value)); + appendStringInfo(&buf, " %s=\"%s\"", xmlExpr->named_args_ncache[i], outstr); + pfree(outstr); + } + i++; + } + if (xmlExpr->args) + { + ExprState *expr = linitial(xmlExpr->args); + Datum value = ExecEvalExpr(expr, econtext, &isnull, NULL); + + if (!isnull) + { + char *outstr = DatumGetCString(OidFunctionCall1(xmlExpr->arg_typeout, value)); + if (state == 0) + { + appendStringInfoChar(&buf, '>'); + state = 1; + } + appendStringInfo(&buf, "%s", outstr); + pfree(outstr); + } + } + + if (state == 0) + appendStringInfo(&buf, "/>"); + else if (state == 1) + appendStringInfo(&buf, "</%s>", xmlExpr->name); + + } + break; + + case IS_XMLFOREST: + { + /* only if all argumets are null returns null */ + int i = 0; + *isNull = true; + foreach(arg, xmlExpr->named_args) + { + GenericExprState *gstate = (GenericExprState *) lfirst(arg); + Datum value = ExecEvalExpr(gstate->arg, econtext, &isnull, NULL); + if (!isnull) + { + char *outstr = DatumGetCString(OidFunctionCall1(xmlExpr->named_args_tcache[i], value)); + appendStringInfo(&buf, "<%s>%s</%s>", xmlExpr->named_args_ncache[i], outstr, xmlExpr->named_args_ncache[i]); + pfree(outstr); + *isNull = false; + } + i += 1; + } + } + break; + default: + break; + } + + len = buf.len + VARHDRSZ; + result = palloc(len); + VARATT_SIZEP(result) = len; + memcpy(VARDATA(result), buf.data, buf.len); + pfree(buf.data); + PG_RETURN_TEXT_P(result); +} + /* * ExecEvalCoerceToDomain * @@ -3668,6 +3785,64 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) mstate; } break; + case T_XmlExpr: + { + List *outlist; + ListCell *arg; + XmlExpr *xexpr = (XmlExpr *) node; + XmlExprState *xstate = makeNode(XmlExprState); + int i = 0; + Oid typeout; + + xstate->name = xexpr->name; + + xstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalXml; + xstate->op = xexpr->op; + + outlist = NIL; + if (xexpr->named_args) + { + xstate->named_args_tcache = (Oid *) palloc(list_length(xexpr->named_args) * sizeof(int)); + xstate->named_args_ncache = (char **) palloc(list_length(xexpr->named_args) * sizeof(char *)); + + i = 0; + foreach(arg, xexpr->named_args) + { + bool tpisvarlena; + Expr *e = (Expr *) lfirst(arg); + ExprState *estate = ExecInitExpr(e, parent); + TargetEntry *tle; + outlist = lappend(outlist, estate); + tle = (TargetEntry *) ((GenericExprState *) estate)->xprstate.expr; + getTypeOutputInfo(exprType((Node *)tle->expr), &typeout, &tpisvarlena); + xstate->named_args_ncache[i] = tle->resname; + xstate->named_args_tcache[i] = typeout; + i++; + } + } + else + { + xstate->named_args_tcache = NULL; + xstate->named_args_ncache = NULL; + } + xstate->named_args = outlist; + + outlist = NIL; + foreach(arg, xexpr->args) + { + bool tpisvarlena; + ExprState *estate; + Expr *e = (Expr *) lfirst(arg); + getTypeOutputInfo(exprType((Node *)e), &typeout, &tpisvarlena); + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + xstate->arg_typeout = typeout; + xstate->args = outlist; + + state = (ExprState *) xstate; + } + break; case T_NullIfExpr: { NullIfExpr *nullifexpr = (NullIfExpr *) node; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 5047dc8ad7..3bb95b658d 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.354 2006/12/10 22:13:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.355 2006/12/21 16:05:13 petere Exp $ * *------------------------------------------------------------------------- */ @@ -1136,6 +1136,22 @@ _copyBooleanTest(BooleanTest *from) return newnode; } +/* + * _copyXmlExpr + */ +static XmlExpr * +_copyXmlExpr(XmlExpr *from) +{ + XmlExpr *newnode = makeNode(XmlExpr); + + COPY_SCALAR_FIELD(op); + COPY_STRING_FIELD(name); + COPY_NODE_FIELD(named_args); + COPY_NODE_FIELD(args); + + return newnode; +} + /* * _copyCoerceToDomain */ @@ -2966,6 +2982,9 @@ copyObject(void *from) case T_BooleanTest: retval = _copyBooleanTest(from); break; + case T_XmlExpr: + retval = _copyXmlExpr(from); + break; case T_CoerceToDomain: retval = _copyCoerceToDomain(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index e341b74f3e..ef21e67faf 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.288 2006/12/10 22:13:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.289 2006/12/21 16:05:13 petere Exp $ * *------------------------------------------------------------------------- */ @@ -495,6 +495,17 @@ _equalBooleanTest(BooleanTest *a, BooleanTest *b) return true; } +static bool +_equalXmlExpr(XmlExpr *a, XmlExpr *b) +{ + COMPARE_SCALAR_FIELD(op); + COMPARE_STRING_FIELD(name); + COMPARE_NODE_FIELD(named_args); + COMPARE_NODE_FIELD(args); + + return true; +} + static bool _equalCoerceToDomain(CoerceToDomain *a, CoerceToDomain *b) { @@ -1968,6 +1979,9 @@ equal(void *a, void *b) case T_BooleanTest: retval = _equalBooleanTest(a, b); break; + case T_XmlExpr: + retval = _equalXmlExpr(a, b); + break; case T_CoerceToDomain: retval = _equalCoerceToDomain(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index daeb3fe872..5ddf60dbbb 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.286 2006/12/10 22:13:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.287 2006/12/21 16:05:13 petere Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -920,6 +920,17 @@ _outBooleanTest(StringInfo str, BooleanTest *node) WRITE_ENUM_FIELD(booltesttype, BoolTestType); } +static void +_outXmlExpr(StringInfo str, XmlExpr *node) +{ + WRITE_NODE_TYPE("XMLEXPR"); + + WRITE_ENUM_FIELD(op, XmlExprOp); + WRITE_STRING_FIELD(name); + WRITE_NODE_FIELD(named_args); + WRITE_NODE_FIELD(args); +} + static void _outCoerceToDomain(StringInfo str, CoerceToDomain *node) { @@ -2019,6 +2030,9 @@ _outNode(StringInfo str, void *obj) case T_BooleanTest: _outBooleanTest(str, obj); break; + case T_XmlExpr: + _outXmlExpr(str, obj); + break; case T_CoerceToDomain: _outCoerceToDomain(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index df0a217027..689cef3edf 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.196 2006/12/10 22:13:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.197 2006/12/21 16:05:13 petere Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -764,6 +764,22 @@ _readBooleanTest(void) READ_DONE(); } +/* + * _readXmlExpr + */ +static XmlExpr * +_readXmlExpr(void) +{ + READ_LOCALS(XmlExpr); + + READ_ENUM_FIELD(op, XmlExprOp); + READ_STRING_FIELD(name); + READ_NODE_FIELD(named_args); + READ_NODE_FIELD(args); + + READ_DONE(); +} + /* * _readCoerceToDomain */ @@ -1014,6 +1030,8 @@ parseNodeString(void) return_value = _readNullTest(); else if (MATCH("BOOLEANTEST", 11)) return_value = _readBooleanTest(); + else if (MATCH("XMLEXPR", 7)) + return_value = _readXmlExpr(); else if (MATCH("COERCETODOMAIN", 14)) return_value = _readCoerceToDomain(); else if (MATCH("COERCETODOMAINVALUE", 19)) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 3800228398..73ad926418 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.223 2006/10/25 22:11:32 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.224 2006/12/21 16:05:13 petere Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -559,6 +559,8 @@ expression_returns_set_walker(Node *node, void *context) return false; if (IsA(node, NullIfExpr)) return false; + if (IsA(node, XmlExpr)) + return false; return expression_tree_walker(node, expression_returns_set_walker, context); @@ -876,6 +878,8 @@ contain_nonstrict_functions_walker(Node *node, void *context) return true; if (IsA(node, BooleanTest)) return true; + if (IsA(node, XmlExpr)) + return true; return expression_tree_walker(node, contain_nonstrict_functions_walker, context); } @@ -3334,6 +3338,16 @@ expression_tree_walker(Node *node, return walker(((NullTest *) node)->arg, context); case T_BooleanTest: return walker(((BooleanTest *) node)->arg, context); + case T_XmlExpr: + { + XmlExpr *xexpr = (XmlExpr *) node; + + if (walker(xexpr->named_args, context)) + return true; + if (walker(xexpr->args, context)) + return true; + } + break; case T_CoerceToDomain: return walker(((CoerceToDomain *) node)->arg, context); case T_TargetEntry: @@ -3857,6 +3871,17 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_XmlExpr: + { + XmlExpr *xexpr = (XmlExpr *) node; + XmlExpr *newnode; + + FLATCOPY(newnode, xexpr, XmlExpr); + MUTATE(newnode->named_args, xexpr->named_args, List *); + MUTATE(newnode->args, xexpr->args, List *); + return (Node *) newnode; + } + break; case T_NullIfExpr: { NullIfExpr *expr = (NullIfExpr *) node; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c90743a101..cc40040736 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.568 2006/11/05 22:42:09 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.569 2006/12/21 16:05:14 petere Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -106,6 +106,7 @@ static void insertSelectOptions(SelectStmt *stmt, static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg); static Node *doNegate(Node *n, int location); static void doNegateFloat(Value *v); +static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args); %} @@ -345,6 +346,11 @@ static void doNegateFloat(Value *v); %type <str> OptTableSpace OptConsTableSpace OptTableSpaceOwner %type <list> opt_check_option +%type <target> xml_attribute_el +%type <list> xml_attribute_list xml_attributes +%type <node> xml_root_version +%type <ival> opt_xml_root_standalone document_or_content xml_whitespace_option + /* * If you make any token changes, update the keyword table in @@ -365,13 +371,13 @@ static void doNegateFloat(Value *v); CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT COMMITTED CONCURRENTLY CONNECTION CONSTRAINT CONSTRAINTS - CONVERSION_P CONVERT COPY CREATE CREATEDB + CONTENT CONVERSION_P CONVERT COPY CREATE CREATEDB CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS - DESC DISABLE_P DISTINCT DO DOMAIN_P DOUBLE_P DROP + DESC DISABLE_P DISTINCT DO DOCUMENT DOMAIN_P DOUBLE_P DROP EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ESCAPE EXCEPT EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT @@ -398,7 +404,7 @@ static void doNegateFloat(Value *v); MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE - NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB + NAME NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NUMERIC @@ -417,8 +423,8 @@ static void doNegateFloat(Value *v); SAVEPOINT SCHEMA SCROLL SECOND_P SECURITY SELECT SEQUENCE SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE - SHOW SIMILAR SIMPLE SMALLINT SOME STABLE START STATEMENT - STATISTICS STDIN STDOUT STORAGE STRICT_P SUBSTRING SUPERUSER_P SYMMETRIC + SHOW SIMILAR SIMPLE SMALLINT SOME STABLE STANDALONE START STATEMENT + STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP SUBSTRING SUPERUSER_P SYMMETRIC SYSID SYSTEM_P TABLE TABLESPACE TEMP TEMPLATE TEMPORARY THEN TIME TIMESTAMP @@ -428,12 +434,15 @@ static void doNegateFloat(Value *v); UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNTIL UPDATE USER USING - VACUUM VALID VALIDATOR VALUES VARCHAR VARYING - VERBOSE VIEW VOLATILE + VACUUM VALID VALIDATOR VALUE VALUES VARCHAR VARYING + VERBOSE VERSION VIEW VOLATILE + + WHEN WHERE WHITESPACE WITH WITHOUT WORK WRITE - WHEN WHERE WITH WITHOUT WORK WRITE + XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLFOREST XMLPARSE + XMLPI XMLROOT XMLSERIALIZE - YEAR_P + YEAR_P YES ZONE @@ -484,6 +493,7 @@ static void doNegateFloat(Value *v); * left-associativity among the JOIN rules themselves. */ %left JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL +%right PRESERVE STRIP %% /* @@ -7868,6 +7878,146 @@ func_expr: func_name '(' ')' v->op = IS_LEAST; $$ = (Node *)v; } + | XMLCONCAT '(' expr_list ')' + { + $$ = makeXmlExpr(IS_XMLCONCAT, NULL, NULL, $3); + } + | XMLELEMENT '(' NAME ColLabel ')' + { + $$ = makeXmlExpr(IS_XMLELEMENT, $4, NULL, NULL); + } + | XMLELEMENT '(' NAME ColLabel ',' xml_attributes ')' + { + $$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, NULL); + } + | XMLELEMENT '(' NAME ColLabel ',' expr_list ')' + { + $$ = makeXmlExpr(IS_XMLELEMENT, $4, NULL, $6); + } + | XMLELEMENT '(' NAME ColLabel ',' xml_attributes ',' expr_list ')' + { + $$ = makeXmlExpr(IS_XMLELEMENT, $4, $6, $8); + } + | XMLFOREST '(' xml_attribute_list ')' + { + $$ = makeXmlExpr(IS_XMLFOREST, NULL, $3, NULL); + } + | XMLPARSE '(' document_or_content a_expr xml_whitespace_option ')' + { + FuncCall *n = makeNode(FuncCall); + n->funcname = SystemFuncName("xmlparse"); + n->args = list_make3(makeBoolAConst($3 == DOCUMENT), $4, makeBoolAConst($5 == PRESERVE)); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + n->location = @1; + $$ = (Node *)n; + } + | XMLPI '(' NAME ColLabel ')' + { + FuncCall *n = makeNode(FuncCall); + n->funcname = SystemFuncName("xmlpi"); + n->args = list_make1(makeStringConst($4, NULL)); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + n->location = @1; + $$ = (Node *)n; + } + | XMLPI '(' NAME ColLabel ',' a_expr ')' + { + FuncCall *n = makeNode(FuncCall); + n->funcname = SystemFuncName("xmlpi"); + n->args = list_make2(makeStringConst($4, NULL), $6); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + n->location = @1; + $$ = (Node *)n; + } + | XMLROOT '(' a_expr ',' xml_root_version opt_xml_root_standalone ')' + { + FuncCall *n = makeNode(FuncCall); + Node *ver; + A_Const *sa; + + if ($5) + ver = $5; + else + { + A_Const *val; + + val = makeNode(A_Const); + val->val.type = T_Null; + ver = (Node *) val; + } + + if ($6) + sa = makeBoolAConst($6 == 1); + else + { + sa = makeNode(A_Const); + sa->val.type = T_Null; + } + + n->funcname = SystemFuncName("xmlroot"); + n->args = list_make3($3, ver, sa); + n->agg_star = FALSE; + n->agg_distinct = FALSE; + n->location = @1; + $$ = (Node *)n; + } + | XMLSERIALIZE '(' document_or_content a_expr AS Typename ')' + { + /* + * FIXME: This should be made distinguishable from + * CAST (for reverse compilation at least). + */ + $$ = makeTypeCast($4, $6); + } + ; + +/* + * SQL/XML support + */ +xml_root_version: VERSION a_expr { $$ = $2; } + | VERSION NO VALUE { $$ = NULL; } + ; + +opt_xml_root_standalone: ',' STANDALONE YES { $$ = 1; } + | ',' STANDALONE NO { $$ = -1; } + | ',' STANDALONE NO VALUE { $$ = 0; } + | /*EMPTY*/ { $$ = 0; } + ; + +xml_attributes: XMLATTRIBUTES '(' xml_attribute_list ')' { $$ = $3; } + ; + +xml_attribute_list: xml_attribute_el { $$ = list_make1($1); } + | xml_attribute_list ',' xml_attribute_el { $$ = lappend($1, $3); } + ; + +xml_attribute_el: a_expr AS ColLabel + { + $$ = makeNode(ResTarget); + $$->name = $3; + $$->indirection = NULL; + $$->val = (Node *) $1; + + } + | a_expr + { + $$ = makeNode(ResTarget); + $$->name = NULL; + $$->indirection = NULL; + $$->val = (Node *) $1; + } + ; + +document_or_content: DOCUMENT { $$ = DOCUMENT; } + | CONTENT { $$ = CONTENT; } + ; + +xml_whitespace_option: PRESERVE WHITESPACE { $$ = PRESERVE; } + | STRIP WHITESPACE { $$ = STRIP; } + | /*EMPTY*/ { $$ = STRIP; } ; /* @@ -8562,6 +8712,7 @@ unreserved_keyword: | CONCURRENTLY | CONNECTION | CONSTRAINTS + | CONTENT | CONVERSION_P | COPY | CREATEDB @@ -8581,6 +8732,7 @@ unreserved_keyword: | DELIMITER | DELIMITERS | DISABLE_P + | DOCUMENT | DOMAIN_P | DOUBLE_P | DROP @@ -8640,6 +8792,7 @@ unreserved_keyword: | MODE | MONTH_P | MOVE + | NAME | NAMES | NEXT | NO @@ -8700,12 +8853,14 @@ unreserved_keyword: | SHOW | SIMPLE | STABLE + | STANDALONE | START | STATEMENT | STATISTICS | STDIN | STDOUT | STORAGE + | STRIP | SUPERUSER_P | SYSID | SYSTEM_P @@ -8729,13 +8884,17 @@ unreserved_keyword: | VALID | VALIDATOR | VARYING + | VERSION | VIEW + | VALUE | VOLATILE + | WHITESPACE | WITH | WITHOUT | WORK | WRITE | YEAR_P + | YES | ZONE ; @@ -8788,6 +8947,14 @@ col_name_keyword: | TRIM | VALUES | VARCHAR + | XMLATTRIBUTES + | XMLELEMENT + | XMLCONCAT + | XMLFOREST + | XMLPARSE + | XMLPI + | XMLROOT + | XMLSERIALIZE ; /* Function identifier --- keywords that can be function names. @@ -9322,6 +9489,17 @@ doNegateFloat(Value *v) } } +static Node * +makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args) +{ + XmlExpr *x = makeNode(XmlExpr); + x->op = op; + x->name = name; + x->named_args = named_args; + x->args = args; + return (Node *) x; +} + /* * Must undefine base_yylex before including scan.c, since we want it * to create the function base_yylex not filtered_base_yylex. diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index 50fd3aac40..b5e49e955f 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.177 2006/10/07 21:51:02 petere Exp $ + * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.178 2006/12/21 16:05:14 petere Exp $ * *------------------------------------------------------------------------- */ @@ -89,6 +89,7 @@ static const ScanKeyword ScanKeywords[] = { {"connection", CONNECTION}, {"constraint", CONSTRAINT}, {"constraints", CONSTRAINTS}, + {"content", CONTENT}, {"conversion", CONVERSION_P}, {"convert", CONVERT}, {"copy", COPY}, @@ -123,6 +124,7 @@ static const ScanKeyword ScanKeywords[] = { {"disable", DISABLE_P}, {"distinct", DISTINCT}, {"do", DO}, + {"document", DOCUMENT}, {"domain", DOMAIN_P}, {"double", DOUBLE_P}, {"drop", DROP}, @@ -218,6 +220,7 @@ static const ScanKeyword ScanKeywords[] = { {"mode", MODE}, {"month", MONTH_P}, {"move", MOVE}, + {"name", NAME}, {"names", NAMES}, {"national", NATIONAL}, {"natural", NATURAL}, @@ -314,6 +317,7 @@ static const ScanKeyword ScanKeywords[] = { {"smallint", SMALLINT}, {"some", SOME}, {"stable", STABLE}, + {"standalone", STANDALONE}, {"start", START}, {"statement", STATEMENT}, {"statistics", STATISTICS}, @@ -321,6 +325,7 @@ static const ScanKeyword ScanKeywords[] = { {"stdout", STDOUT}, {"storage", STORAGE}, {"strict", STRICT_P}, + {"strip", STRIP}, {"substring", SUBSTRING}, {"superuser", SUPERUSER_P}, {"symmetric", SYMMETRIC}, @@ -357,19 +362,31 @@ static const ScanKeyword ScanKeywords[] = { {"vacuum", VACUUM}, {"valid", VALID}, {"validator", VALIDATOR}, + {"value", VALUE}, {"values", VALUES}, {"varchar", VARCHAR}, {"varying", VARYING}, {"verbose", VERBOSE}, + {"version", VERSION}, {"view", VIEW}, {"volatile", VOLATILE}, {"when", WHEN}, {"where", WHERE}, + {"whitespace", WHITESPACE}, {"with", WITH}, {"without", WITHOUT}, {"work", WORK}, {"write", WRITE}, + {"xmlattributes", XMLATTRIBUTES}, + {"xmlconcat", XMLCONCAT}, + {"xmlelement", XMLELEMENT}, + {"xmlforest", XMLFOREST}, + {"xmlparse", XMLPARSE}, + {"xmlpi", XMLPI}, + {"xmlroot", XMLROOT}, + {"xmlserialize", XMLSERIALIZE}, {"year", YEAR_P}, + {"yes", YES}, {"zone", ZONE}, }; diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 2a468c6827..5670ed4fe7 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.147 2006/12/10 22:13:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.148 2006/12/21 16:05:14 petere Exp $ * *------------------------------------------------------------------------- */ @@ -919,6 +919,46 @@ coerce_to_bigint(ParseState *pstate, Node *node, return node; } +/* + * coerce_to_xml() + * Coerce an argument of a construct that requires xml input. + * Also check that input is not a set. + * + * Returns the possibly-transformed node tree. + * + * As with coerce_type, pstate may be NULL if no special unknown-Param + * processing is wanted. + */ +Node * +coerce_to_xml(ParseState *pstate, Node *node, + const char *constructName) +{ + Oid inputTypeId = exprType(node); + + if (inputTypeId != XMLOID) + { + node = coerce_to_target_type(pstate, node, inputTypeId, + XMLOID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST); + if (node == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + /* translator: first %s is name of a SQL construct, eg LIMIT */ + errmsg("argument of %s must be type xml, not type %s", + constructName, format_type_be(inputTypeId)))); + } + + if (expression_returns_set(node)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + /* translator: %s is name of a SQL construct, eg LIMIT */ + errmsg("argument of %s must not return a set", + constructName))); + + return node; +} + /* select_common_type() * Determine the common supertype of a list of input expression types. diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index b1b6ea8145..234a15b6af 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.199 2006/12/10 22:13:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.200 2006/12/21 16:05:14 petere Exp $ * *------------------------------------------------------------------------- */ @@ -33,6 +33,7 @@ #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/xml.h" bool Transform_null_equals = false; @@ -55,6 +56,7 @@ static Node *transformArrayExpr(ParseState *pstate, ArrayExpr *a); 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 *transformBooleanTest(ParseState *pstate, BooleanTest *b); static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref); static Node *transformWholeRowRef(ParseState *pstate, char *schemaname, @@ -232,6 +234,10 @@ transformExpr(ParseState *pstate, Node *expr) result = transformBooleanTest(pstate, (BooleanTest *) expr); break; + case T_XmlExpr: + result = transformXmlExpr(pstate, (XmlExpr *) expr); + break; + /********************************************* * Quietly accept node types that may be presented when we are * called on an already-transformed tree. @@ -1409,6 +1415,56 @@ transformBooleanTest(ParseState *pstate, BooleanTest *b) return (Node *) b; } +static Node * +transformXmlExpr(ParseState *pstate, XmlExpr *x) +{ + ListCell *lc; + XmlExpr *newx = makeNode(XmlExpr); + + newx->op = x->op; + if (x->name) + newx->name = map_sql_identifier_to_xml_name(x->name, false); + else + newx->name = NULL; + + foreach(lc, x->named_args) + { + ResTarget *r = (ResTarget *) lfirst(lc); + Node *expr = transformExpr(pstate, r->val); + char *argname = NULL; + + if (r->name) + argname = map_sql_identifier_to_xml_name(r->name, false); + else if (IsA(r->val, ColumnRef)) + argname = map_sql_identifier_to_xml_name(FigureColname(r->val), true); + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + x->op == IS_XMLELEMENT + ? errmsg("unnamed attribute value must be a column reference") + : errmsg("unnamed element value must be a column reference"))); + + newx->named_args = lappend(newx->named_args, + makeTargetEntry((Expr *) expr, 0, argname, false)); + } + + foreach(lc, x->args) + { + Node *e = (Node *) lfirst(lc); + Node *newe; + + newe = coerce_to_xml(pstate, transformExpr(pstate, e), + (x->op == IS_XMLCONCAT + ? "XMLCONCAT" + : (x->op == IS_XMLELEMENT + ? "XMLELEMENT" + : "XMLFOREST"))); + newx->args = lappend(newx->args, newe); + } + + return (Node *) newx; +} + /* * Construct a whole-row reference to represent the notation "relation.*". * @@ -1668,6 +1724,9 @@ exprType(Node *expr) case T_BooleanTest: type = BOOLOID; break; + case T_XmlExpr: + type = XMLOID; + break; case T_CoerceToDomain: type = ((CoerceToDomain *) expr)->resulttype; break; diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index bb4b065eeb..906d96e45c 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.149 2006/10/04 00:29:56 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.150 2006/12/21 16:05:14 petere Exp $ * *------------------------------------------------------------------------- */ @@ -1315,6 +1315,21 @@ FigureColnameInternal(Node *node, char **name) return 2; } break; + case T_XmlExpr: + /* make SQL/XML functions act like a regular function */ + switch (((XmlExpr*) node)->op) + { + case IS_XMLCONCAT: + *name = "xmlconcat"; + return 2; + case IS_XMLELEMENT: + *name = "xmlelement"; + return 2; + case IS_XMLFOREST: + *name = "xmlforest"; + return 2; + } + break; default: break; } diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 5a1996c343..11a03f3185 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -1,7 +1,7 @@ # # Makefile for utils/adt # -# $PostgreSQL: pgsql/src/backend/utils/adt/Makefile,v 1.60 2006/04/05 22:11:55 tgl Exp $ +# $PostgreSQL: pgsql/src/backend/utils/adt/Makefile,v 1.61 2006/12/21 16:05:15 petere Exp $ # subdir = src/backend/utils/adt @@ -25,7 +25,7 @@ OBJS = acl.o arrayfuncs.o array_userfuncs.o arrayutils.o bool.o \ tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \ network.o mac.o inet_net_ntop.o inet_net_pton.o \ ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \ - ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o + ascii.o quote.o pgstatfuncs.o encode.o dbsize.o genfile.o xml.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 9d9404bde4..a99942010b 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -2,7 +2,7 @@ * ruleutils.c - Functions to convert stored expressions/querytrees * back to source text * - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.235 2006/11/10 22:59:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.236 2006/12/21 16:05:15 petere Exp $ **********************************************************************/ #include "postgres.h" @@ -2988,6 +2988,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_CoalesceExpr: case T_MinMaxExpr: case T_NullIfExpr: + case T_XmlExpr: case T_Aggref: case T_FuncExpr: /* function-like: name(..) or name[..] */ @@ -3096,6 +3097,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_CoalesceExpr: /* own parentheses */ case T_MinMaxExpr: /* own parentheses */ case T_NullIfExpr: /* other separators */ + case T_XmlExpr: /* own parentheses */ case T_Aggref: /* own parentheses */ case T_CaseExpr: /* other separators */ return true; @@ -3144,6 +3146,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_CoalesceExpr: /* own parentheses */ case T_MinMaxExpr: /* own parentheses */ case T_NullIfExpr: /* other separators */ + case T_XmlExpr: /* own parentheses */ case T_Aggref: /* own parentheses */ case T_CaseExpr: /* other separators */ return true; @@ -3845,6 +3848,28 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_XmlExpr: + { + XmlExpr *xexpr = (XmlExpr *) node; + + switch (xexpr->op) + { + case IS_XMLCONCAT: + appendStringInfo(buf, "XMLCONCAT("); + break; + case IS_XMLELEMENT: + appendStringInfo(buf, "XMLELEMENT("); + break; + case IS_XMLFOREST: + appendStringInfo(buf, "XMLFOREST("); + break; + } + get_rule_expr((Node *) xexpr->named_args, context, true); + get_rule_expr((Node *) xexpr->args, context, true); + appendStringInfoChar(buf, ')'); + } + break; + case T_CoerceToDomain: { CoerceToDomain *ctest = (CoerceToDomain *) node; diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c new file mode 100644 index 0000000000..8997730fc8 --- /dev/null +++ b/src/backend/utils/adt/xml.c @@ -0,0 +1,942 @@ +/*------------------------------------------------------------------------- + * + * xml.c + * XML data type support. + * + * + * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.1 2006/12/21 16:05:15 petere Exp $ + * + *------------------------------------------------------------------------- + */ + +/* + * Generally, XML type support is only available when libxml use was + * configured during the build. But even if that is not done, the + * type and all the functions are available, but most of them will + * fail. For one thing, this avoids having to manage variant catalog + * installations. But it also has nice effects such as that you can + * dump a database containing XML type data even if the server is not + * linked with libxml. + */ + +#include "postgres.h" + +#ifdef USE_LIBXML +#include <libxml/chvalid.h> +#include <libxml/parser.h> +#include <libxml/tree.h> +#include <libxml/uri.h> +#include <libxml/xmlerror.h> +#endif /* USE_LIBXML */ + +#include "fmgr.h" +#include "mb/pg_wchar.h" +#include "nodes/execnodes.h" +#include "utils/builtins.h" +#include "utils/xml.h" + + +#ifdef USE_LIBXML + +/* + * A couple of useful macros (similar to ones from libxml/parse.c) + */ +#define CMP4( s, c1, c2, c3, c4 ) \ + ( ((unsigned char *) s)[ 0 ] == c1 && ((unsigned char *) s)[ 1 ] == c2 && \ + ((unsigned char *) s)[ 2 ] == c3 && ((unsigned char *) s)[ 3 ] == c4 ) +#define CMP5( s, c1, c2, c3, c4, c5 ) \ + ( CMP4( s, c1, c2, c3, c4 ) && ((unsigned char *) s)[ 4 ] == c5 ) + +#define PG_XML_DEFAULT_URI "dummy.xml" +#define XML_ERRBUF_SIZE 200 + + +static void xml_init(void); +static void *xml_palloc(size_t size); +static void *xml_repalloc(void *ptr, size_t size); +static void xml_pfree(void *ptr); +static char *xml_pstrdup(const char *string); +static void xml_ereport(int level, char *msg, void *ctxt); +static void xml_errorHandler(void *ctxt, const char *msg, ...); +static void xml_ereport_by_code(int level, char *msg, int errcode); +static xmlChar *xml_text2xmlChar(text *in); +static xmlDocPtr xml_parse(text *data, int opts, bool is_document); + + +/* Global variables */ +/* taken from contrib/xml2 */ +/* FIXME: DO NOT USE global vars !!! */ +char *xml_errbuf; /* per line error buffer */ +char *xml_errmsg = NULL; /* overall error message */ + +#endif /* USE_LIBXML */ + + +#define NO_XML_SUPPORT() ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("no XML support in this installation"))) + + +Datum +xml_in(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + char *s = PG_GETARG_CSTRING(0); + size_t len; + xmltype *vardata; + + len = strlen(s); + vardata = palloc(len + VARHDRSZ); + VARATT_SIZEP(vardata) = len + VARHDRSZ; + memcpy(VARDATA(vardata), s, len); + + /* + * Parse the data to check if it is well-formed XML data. Assume + * that ERROR occurred if parsing failed. Do we need DTD + * validation (if DTD exists)? + */ + xml_parse(vardata, XML_PARSE_DTDATTR | XML_PARSE_DTDVALID, false); + + PG_RETURN_XML_P(vardata); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} + + +Datum +xml_out(PG_FUNCTION_ARGS) +{ + xmltype *s = PG_GETARG_XML_P(0); + char *result; + int32 len; + + len = VARSIZE(s) - VARHDRSZ; + result = palloc(len + 1); + memcpy(result, VARDATA(s), len); + result[len] = '\0'; + + PG_RETURN_CSTRING(result); +} + + +#ifdef USE_LIBXML +static void +appendStringInfoText(StringInfo str, const text *t) +{ + appendBinaryStringInfo(str, VARDATA(t), VARSIZE(t) - VARHDRSZ); +} + + +static xmltype * +stringinfo_to_xmltype(StringInfo buf) +{ + int32 len; + xmltype *result; + + len = buf->len + VARHDRSZ; + result = palloc(len); + VARATT_SIZEP(result) = len; + memcpy(VARDATA(result), buf->data, buf->len); + + return result; +} +#endif + + +Datum +xmlcomment(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *arg = PG_GETARG_TEXT_P(0); + int len = VARATT_SIZEP(arg) - VARHDRSZ; + StringInfoData buf; + int i; + + /* check for "--" in string or "-" at the end */ + for (i = 1; i < len; i++) + if ((VARDATA(arg)[i] == '-' && VARDATA(arg)[i - 1] == '-') + || (VARDATA(arg)[i] == '-' && i == len - 1)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_XML_COMMENT), + errmsg("invalid XML comment"))); + + initStringInfo(&buf); + appendStringInfo(&buf, "<!--"); + appendStringInfoText(&buf, arg); + appendStringInfo(&buf, "-->"); + + PG_RETURN_XML_P(stringinfo_to_xmltype(&buf)); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} + + +Datum +xmlparse(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *data; + bool is_document; + bool preserve_whitespace; + + data = PG_GETARG_TEXT_P(0); + + if (PG_NARGS() >= 2) + is_document = PG_GETARG_BOOL(1); + else + is_document = false; + + if (PG_NARGS() >= 3) + preserve_whitespace = PG_GETARG_BOOL(2); + else + /* + * Since the XMLPARSE grammar makes STRIP WHITESPACE the + * default, this argument should really default to false. But + * until we have actually implemented whitespace stripping, + * this would be annoying. + */ + preserve_whitespace = true; + + if (!preserve_whitespace) + ereport(WARNING, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("XMLPARSE with STRIP WHITESPACE is not implemented"))); + + /* + * Note, that here we try to apply DTD defaults + * (XML_PARSE_DTDATTR) according to SQL/XML:10.16.7.d: 'Default + * valies defined by internal DTD are applied'. As for external + * DTDs, we try to support them too, (see SQL/XML:10.16.7.e) + */ + xml_parse(data, XML_PARSE_DTDATTR, is_document); /* assume that ERROR occurred if parsing failed */ + + PG_RETURN_XML_P(data); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} + + +Datum +xmlpi(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + char *target = NameStr(*PG_GETARG_NAME(0)); + StringInfoData buf; + + if (strlen(target) >= 3 + && (target[0] == 'x' || target[0] == 'X') + && (target[1] == 'm' || target[1] == 'M') + && (target[2] == 'l' || target[2] == 'L')) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid XML processing instruction"), + errdetail("XML processing instruction target name cannot start with \"xml\"."))); + } + + initStringInfo(&buf); + + appendStringInfo(&buf, "<?"); + appendStringInfoString(&buf, map_sql_identifier_to_xml_name(target, false)); + if (PG_NARGS() > 1) + { + text *arg = PG_GETARG_TEXT_P(1); + char *string; + + string = DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(arg))); + if (strstr(string, "?>")) + ereport(ERROR, + (errcode(ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION), + errmsg("invalid XML processing instruction"), + errdetail("XML processing instruction cannot contain \"?>\"."))); + + appendStringInfoString(&buf, " "); + appendStringInfoString(&buf, string); + } + appendStringInfoString(&buf, "?>"); + + PG_RETURN_XML_P(stringinfo_to_xmltype(&buf)); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} + + +Datum +xmlroot(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + xmltype *data; + text *version; + int standalone; + StringInfoData buf; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + else + data = PG_GETARG_XML_P(0); + + if (PG_ARGISNULL(1)) + version = NULL; + else + version = PG_GETARG_TEXT_P(1); + + if (PG_ARGISNULL(2)) + standalone = 0; + else + { + bool tmp = PG_GETARG_BOOL(2); + standalone = (tmp ? 1 : -1); + } + + /* + * FIXME: This is probably supposed to be cleverer if there + * already is an XML preamble. + */ + initStringInfo(&buf); + + appendStringInfo(&buf,"<?xml"); + if (version) { + appendStringInfo(&buf, " version=\""); + appendStringInfoText(&buf, version); + appendStringInfo(&buf, "\""); + } + if (standalone) + appendStringInfo(&buf, " standalone=\"%s\"", (standalone == 1 ? "yes" : "no")); + appendStringInfo(&buf, "?>"); + appendStringInfoText(&buf, (text *) data); + + PG_RETURN_XML_P(stringinfo_to_xmltype(&buf)); +#else + NO_XML_SUPPORT(); + return 0; +#endif +} + + +/* + * Validate document (given as string) against DTD (given as external link) + * TODO !!! use text instead of cstring for second arg + * TODO allow passing DTD as a string value (not only as an URI) + * TODO redesign (see comment with '!!!' below) + */ +Datum +xmlvalidate(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXML + text *data = PG_GETARG_TEXT_P(0); + text *dtdOrUri = PG_GETARG_TEXT_P(1); + bool result = FALSE; + xmlParserCtxtPtr ctxt; /* the parser context */ + xmlDocPtr doc; /* the resulting document tree */ + xmlDtdPtr dtd; + + xml_init(); + + ctxt = xmlNewParserCtxt(); + if (ctxt == NULL) + xml_ereport(ERROR, "could not allocate parser context", ctxt); + doc = xmlCtxtReadMemory(ctxt, (char *) VARDATA(data), + VARSIZE(data) - VARHDRSZ, PG_XML_DEFAULT_URI, NULL, 0); + if (doc == NULL) + xml_ereport(ERROR, "could not parse XML data", ctxt); + +#if 0 + uri = xmlCreateURI(); + ereport(NOTICE, (errcode(0),errmsg(" dtd - %s", dtdOrUri))); + dtd = palloc(sizeof(xmlDtdPtr)); + uri = xmlParseURI(dtdOrUri); + if (uri == NULL) + xml_ereport(ERROR, "not implemented yet... (TODO)", ctxt); + else +#endif + dtd = xmlParseDTD(NULL, xml_text2xmlChar(dtdOrUri)); + + if (dtd == NULL) + { +#if 0 + xmlFreeDoc(doc); + xmlFreeParserCtxt(ctxt); +#endif + xml_ereport(ERROR, "could not load DTD", ctxt); + } + + if (xmlValidateDtd(xmlNewValidCtxt(), doc, dtd) == 1) + result = TRUE; + +#if 0 + xmlFreeURI(uri); + xmlFreeDtd(dtd); + xmlFreeDoc(doc); + xmlFreeParserCtxt(ctxt); + xmlCleanupParser(); +#endif + + if (!result) + xml_ereport(NOTICE, "validation against DTD failed", ctxt); + + PG_RETURN_BOOL(result); +#else /* not USE_LIBXML */ + NO_XML_SUPPORT(); + return 0; +#endif /* not USE_LIBXML */ +} + + +#ifdef USE_LIBXML + +/* + * Container for some init stuff (not good design!) + * TODO xmlChar is utf8-char, make proper tuning (initdb with enc!=utf8 and check) + */ +static void +xml_init(void) +{ + /* + * Currently, we have no pure UTF-8 support for internals -- check + * if we can work. + */ + if (sizeof (char) != sizeof (xmlChar)) + ereport(ERROR, + (errmsg("cannot initialize XML library"), + errdetail("libxml2 has incompatible char type: sizeof(char)=%u, sizeof(xmlChar)=%u.", + sizeof(char), sizeof(xmlChar)))); + + xmlMemSetup(xml_pfree, xml_palloc, xml_repalloc, xml_pstrdup); + xmlInitParser(); + LIBXML_TEST_VERSION; + /* do not flood PG's logfile with libxml error messages - reset error handler*/ + xmlSetGenericErrorFunc(NULL, xml_errorHandler); + xml_errmsg = NULL; + xml_errbuf = palloc(XML_ERRBUF_SIZE); + memset(xml_errbuf, 0, XML_ERRBUF_SIZE); +} + + +/* + * Convert a C string to XML internal representation + * (same things as for TEXT, but with checking the data for well-formedness + * and, moreover, validation against DTD, if needed). + * NOTICE: We use TEXT type as internal storage type. In the future, + * we plan to create own storage type (maybe several types/strategies) + * TODO predefined DTDs / XSDs and validation + * TODO validation against XML Schema + * TODO maybe, libxml2's xmlreader is better? (do not construct DOM, yet do not use SAX - see xml_reader.c) + * TODO what about internal URI for docs? (see PG_XML_DEFAULT_URI below) + */ +static xmlDocPtr +xml_parse(text *data, int opts, bool is_document) +{ + bool validationFailed = FALSE; + xmlParserCtxtPtr ctxt; /* the parser context */ + xmlDocPtr doc; /* the resulting document tree */ + int res_code; + int32 len; + xmlChar *string; +#ifdef XML_DEBUG_DTD_CONST + xmlDtdPtr dtd; /* pointer to DTD */ +#endif + + xml_init(); + + len = VARSIZE(data) - VARHDRSZ; /* will be useful later */ + string = xml_text2xmlChar(data); + + ctxt = xmlNewParserCtxt(); + if (ctxt == NULL) + xml_ereport(ERROR, "could not allocate parser context", ctxt); + + /* first, we try to parse the string as it is XML doc, then, as XML chunk */ + ereport(DEBUG3, (errmsg("string to parse: %s", string))); + if (len > 4 && CMP5(string, '<', '?', 'x', 'm', 'l')) + { + /* consider it as DOCUMENT */ + doc = xmlCtxtReadMemory(ctxt, string, len, PG_XML_DEFAULT_URI, NULL, opts); + if (doc == NULL) + { + xml_ereport(ERROR, "could not parse XML data", ctxt); +#if 0 + xmlFreeParserCtxt(ctxt); + xmlCleanupParser(); + ereport(ERROR, (errmsg("could not parse XML data"))); +#endif + } + } + else + { + /* attempt to parse the string as if it is an XML fragment */ + ereport(DEBUG3, (errmsg("the string is not an XML doc, trying to parse as a CHUNK"))); + doc = xmlNewDoc(NULL); + /* TODO resolve: xmlParseBalancedChunkMemory assumes that string is UTF8 encoded! */ + res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0, string, NULL); + if (res_code != 0) + { + xmlFreeParserCtxt(ctxt); + xmlCleanupParser(); + xml_ereport_by_code(ERROR, "could not parse XML data", res_code); + } + } + +#ifdef XML_DEBUG_DTD_CONST + dtd = xmlParseDTD(NULL, (xmlChar *) XML_DEBUG_DTD_CONST); + xml_ereport(DEBUG3, "solid path to DTD was defined for debugging purposes", ctxt); + if (dtd == NULL) + { + xml_ereport(ERROR, "could not parse DTD data", ctxt); + } + else +#else + /* if dtd for our xml data is detected... */ + if ((doc->intSubset != NULL) || (doc->extSubset != NULL)) +#endif + { + /* assume that inline DTD exists - validation should be performed */ +#ifdef XML_DEBUG_DTD_CONST + if (xmlValidateDtd(xmlNewValidCtxt(), doc, dtd) != 1) +#else + if (ctxt->valid == 0) +#endif + { + /* DTD exists, but validator reported 'validation failed' */ + validationFailed = TRUE; + } + } + + if (validationFailed) + xml_ereport(WARNING, "validation against DTD failed", ctxt); + + /* TODO encoding issues + * (thoughts: + * CASE: + * - XML data has explicit encoding attribute in its prolog + * - if not, assume that enc. of XML data is the same as client's one + * + * The common rule is to accept the XML data only if its encoding + * is the same as encoding of the storage (server's). The other possible + * option is to accept all the docs, but DO TRANSFORMATION and, if needed, + * change the prolog. + * + * I think I'd stick the first way (for the 1st version), + * it's much simplier (less errors...) + * ) */ + /* ... */ + + xmlFreeParserCtxt(ctxt); + xmlCleanupParser(); + + ereport(DEBUG3, (errmsg("XML data successfully parsed, encoding: %s", + (char *) doc->encoding))); + + return doc; +} + + +/* + * xmlChar<->text convertions + */ +static xmlChar * +xml_text2xmlChar(text *in) +{ + int32 len = VARSIZE(in) - VARHDRSZ; + xmlChar *res; + + res = palloc(len + 1); + memcpy(res, VARDATA(in), len); + res[len] = '\0'; + + return(res); +} + + +/* + * Wrappers for memory management functions + */ +static void * +xml_palloc(size_t size) +{ + return palloc(size); +} + + +static void * +xml_repalloc(void *ptr, size_t size) +{ + return repalloc(ptr, size); +} + + +static void +xml_pfree(void *ptr) +{ + pfree(ptr); +} + + +static char * +xml_pstrdup(const char *string) +{ + return pstrdup(string); +} + + +/* + * Wrapper for "ereport" function. + * Adds detail - libxml's native error message, if any. + */ +static void +xml_ereport(int level, char *msg, void *ctxt) +{ + char *xmlErrDetail; + int xmlErrLen, i; + xmlErrorPtr libxmlErr = NULL; + + if (xml_errmsg != NULL) + { + ereport(DEBUG1, (errmsg("%s", xml_errmsg))); + pfree(xml_errmsg); + } + + if (ctxt != NULL) + libxmlErr = xmlCtxtGetLastError(ctxt); + + if (libxmlErr == NULL) + { + if (level == ERROR) + { + xmlFreeParserCtxt(ctxt); + xmlCleanupParser(); + } + ereport(level, (errmsg(msg))); + } + else + { + /* as usual, libxml error message contains '\n'; get rid of it */ + xmlErrLen = strlen(libxmlErr->message); /* - 1; */ + xmlErrDetail = (char *) palloc(xmlErrLen); + for (i = 0; i < xmlErrLen; i++) + { + if (libxmlErr->message[i] == '\n') + xmlErrDetail[i] = '.'; + else + xmlErrDetail[i] = libxmlErr->message[i]; + } + if (level == ERROR) + { + xmlFreeParserCtxt(ctxt); + xmlCleanupParser(); + } + ereport(level, (errmsg(msg), errdetail("%s", xmlErrDetail))); + } +} + + +/* + * Error handler for libxml error messages + */ +static void +xml_errorHandler(void *ctxt, const char *msg,...) +{ + va_list args; + + va_start(args, msg); + vsnprintf(xml_errbuf, XML_ERRBUF_SIZE, msg, args); + va_end(args); + /* Now copy the argument across */ + if (xml_errmsg == NULL) + xml_errmsg = pstrdup(xml_errbuf); + else + { + int32 xsize = strlen(xml_errmsg); + + xml_errmsg = repalloc(xml_errmsg, (size_t) (xsize + strlen(xml_errbuf) + 1)); + strncpy(&xml_errmsg[xsize - 1], xml_errbuf, strlen(xml_errbuf)); + xml_errmsg[xsize + strlen(xml_errbuf) - 1] = '\0'; + } + memset(xml_errbuf, 0, XML_ERRBUF_SIZE); +} + + +/* + * Return error message by libxml error code + * TODO make them closer to recommendations from Postgres manual + */ +static void +xml_ereport_by_code(int level, char *msg, int code) +{ + const char *det; + + if (code < 0) + { + ereport(level, (errmsg(msg))); + return; + } + + switch (code) { + case XML_ERR_INTERNAL_ERROR: + det = "libxml internal error"; + break; + case XML_ERR_ENTITY_LOOP: + det = "Detected an entity reference loop"; + break; + case XML_ERR_ENTITY_NOT_STARTED: + det = "EntityValue: \" or ' expected"; + break; + case XML_ERR_ENTITY_NOT_FINISHED: + det = "EntityValue: \" or ' expected"; + break; + case XML_ERR_ATTRIBUTE_NOT_STARTED: + det = "AttValue: \" or ' expected"; + break; + case XML_ERR_LT_IN_ATTRIBUTE: + det = "Unescaped '<' not allowed in attributes values"; + break; + case XML_ERR_LITERAL_NOT_STARTED: + det = "SystemLiteral \" or ' expected"; + break; + case XML_ERR_LITERAL_NOT_FINISHED: + det = "Unfinished System or Public ID \" or ' expected"; + break; + case XML_ERR_MISPLACED_CDATA_END: + det = "Sequence ']]>' not allowed in content"; + break; + case XML_ERR_URI_REQUIRED: + det = "SYSTEM or PUBLIC, the URI is missing"; + break; + case XML_ERR_PUBID_REQUIRED: + det = "PUBLIC, the Public Identifier is missing"; + break; + case XML_ERR_HYPHEN_IN_COMMENT: + det = "Comment must not contain '--' (double-hyphen)"; + break; + case XML_ERR_PI_NOT_STARTED: + det = "xmlParsePI : no target name"; + break; + case XML_ERR_RESERVED_XML_NAME: + det = "Invalid PI name"; + break; + case XML_ERR_NOTATION_NOT_STARTED: + det = "NOTATION: Name expected here"; + break; + case XML_ERR_NOTATION_NOT_FINISHED: + det = "'>' required to close NOTATION declaration"; + break; + case XML_ERR_VALUE_REQUIRED: + det = "Entity value required"; + break; + case XML_ERR_URI_FRAGMENT: + det = "Fragment not allowed"; + break; + case XML_ERR_ATTLIST_NOT_STARTED: + det = "'(' required to start ATTLIST enumeration"; + break; + case XML_ERR_NMTOKEN_REQUIRED: + det = "NmToken expected in ATTLIST enumeration"; + break; + case XML_ERR_ATTLIST_NOT_FINISHED: + det = "')' required to finish ATTLIST enumeration"; + break; + case XML_ERR_MIXED_NOT_STARTED: + det = "MixedContentDecl : '|' or ')*' expected"; + break; + case XML_ERR_PCDATA_REQUIRED: + det = "MixedContentDecl : '#PCDATA' expected"; + break; + case XML_ERR_ELEMCONTENT_NOT_STARTED: + det = "ContentDecl : Name or '(' expected"; + break; + case XML_ERR_ELEMCONTENT_NOT_FINISHED: + det = "ContentDecl : ',' '|' or ')' expected"; + break; + case XML_ERR_PEREF_IN_INT_SUBSET: + det = "PEReference: forbidden within markup decl in internal subset"; + break; + case XML_ERR_GT_REQUIRED: + det = "Expected '>'"; + break; + case XML_ERR_CONDSEC_INVALID: + det = "XML conditional section '[' expected"; + break; + case XML_ERR_EXT_SUBSET_NOT_FINISHED: + det = "Content error in the external subset"; + break; + case XML_ERR_CONDSEC_INVALID_KEYWORD: + det = "conditional section INCLUDE or IGNORE keyword expected"; + break; + case XML_ERR_CONDSEC_NOT_FINISHED: + det = "XML conditional section not closed"; + break; + case XML_ERR_XMLDECL_NOT_STARTED: + det = "Text declaration '<?xml' required"; + break; + case XML_ERR_XMLDECL_NOT_FINISHED: + det = "parsing XML declaration: '?>' expected"; + break; + case XML_ERR_EXT_ENTITY_STANDALONE: + det = "external parsed entities cannot be standalone"; + break; + case XML_ERR_ENTITYREF_SEMICOL_MISSING: + det = "EntityRef: expecting ';'"; + break; + case XML_ERR_DOCTYPE_NOT_FINISHED: + det = "DOCTYPE improperly terminated"; + break; + case XML_ERR_LTSLASH_REQUIRED: + det = "EndTag: '</' not found"; + break; + case XML_ERR_EQUAL_REQUIRED: + det = "Expected '='"; + break; + case XML_ERR_STRING_NOT_CLOSED: + det = "String not closed expecting \" or '"; + break; + case XML_ERR_STRING_NOT_STARTED: + det = "String not started expecting ' or \""; + break; + case XML_ERR_ENCODING_NAME: + det = "Invalid XML encoding name"; + break; + case XML_ERR_STANDALONE_VALUE: + det = "Standalone accepts only 'yes' or 'no'"; + break; + case XML_ERR_DOCUMENT_EMPTY: + det = "Document is empty"; + break; + case XML_ERR_DOCUMENT_END: + det = "Extra content at the end of the document"; + break; + case XML_ERR_NOT_WELL_BALANCED: + det = "Chunk is not well balanced"; + break; + case XML_ERR_EXTRA_CONTENT: + det = "Extra content at the end of well balanced chunk"; + break; + case XML_ERR_VERSION_MISSING: + det = "Malformed declaration expecting version"; + break; + /* more err codes... Please, keep the order! */ + case XML_ERR_ATTRIBUTE_WITHOUT_VALUE: /* 41 */ + det ="Attribute without value"; + break; + case XML_ERR_ATTRIBUTE_REDEFINED: + det ="Attribute defined more than once in the same element"; + break; + case XML_ERR_COMMENT_NOT_FINISHED: /* 45 */ + det = "Comment is not finished"; + break; + case XML_ERR_NAME_REQUIRED: /* 68 */ + det = "Element name not found"; + break; + case XML_ERR_TAG_NOT_FINISHED: /* 77 */ + det = "Closing tag not found"; + break; + default: + det = "Unregistered error (libxml error code: %d)"; + ereport(DEBUG1, (errmsg("Check out \"libxml/xmlerror.h\" and bring errcode \"%d\" processing to \"xml.c\".", code))); + } + + if (xml_errmsg != NULL) + { + ereport(DEBUG1, (errmsg("%s", xml_errmsg))); + pfree(xml_errmsg); + } + + ereport(level, (errmsg(msg), errdetail(det, code))); +} + + +/* + * Convert one char in the current server encoding to a Unicode + * codepoint. + */ +static pg_wchar +sqlchar_to_unicode(unsigned char *s) +{ + int save_enc; + pg_wchar ret; + char *utf8string = pg_do_encoding_conversion(s, pg_mblen(s), GetDatabaseEncoding(), PG_UTF8); + + save_enc = GetDatabaseEncoding(); + SetDatabaseEncoding(PG_UTF8); + pg_mb2wchar_with_len(utf8string, &ret, pg_mblen(s)); + SetDatabaseEncoding(save_enc); + + return ret; +} + + +static bool +is_valid_xml_namefirst(pg_wchar c) +{ + /* (Letter | '_' | ':') */ + return (xmlIsBaseCharQ(c) || xmlIsIdeographicQ(c) + || c == '_' || c == ':'); +} + + +static bool +is_valid_xml_namechar(pg_wchar c) +{ + /* Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender */ + return (xmlIsBaseCharQ(c) || xmlIsIdeographicQ(c) + || xmlIsDigitQ(c) + || c == '.' || c == '-' || c == '_' || c == ':' + || xmlIsCombiningQ(c) + || xmlIsExtenderQ(c)); +} +#endif /* USE_LIBXML */ + + +/* + * Map SQL identifier to XML name; see SQL/XML:2003 section 9.1. + */ +char * +map_sql_identifier_to_xml_name(unsigned char *ident, bool fully_escaped) +{ +#ifdef USE_LIBXML + StringInfoData buf; + unsigned char *p; + + initStringInfo(&buf); + + for (p = ident; *p; p += pg_mblen(p)) + { + if (*p == ':' && (p == ident || fully_escaped)) + appendStringInfo(&buf, "_x003A_"); + else if (*p == '_' && *(p+1) == 'x') + appendStringInfo(&buf, "_x005F_"); + else if (fully_escaped && p == ident + && ( *p == 'x' || *p == 'X') + && ( *(p+1) == 'm' || *(p+1) == 'M') + && ( *(p+2) == 'l' || *(p+2) == 'L')) + { + if (*p == 'x') + appendStringInfo(&buf, "_x0078_"); + else + appendStringInfo(&buf, "_x0058_"); + } + else + { + pg_wchar u = sqlchar_to_unicode(p); + + if (!is_valid_xml_namechar(u) + || (p == ident && !is_valid_xml_namefirst(u))) + appendStringInfo(&buf, "_x%04X_", (unsigned int) u); + else + appendBinaryStringInfo(&buf, p, pg_mblen(p)); + } + } + + return buf.data; +#else /* not USE_LIBXML */ + NO_XML_SUPPORT(); + return NULL; +#endif /* not USE_LIBXML */ +} diff --git a/src/backend/utils/mb/mbutils.c b/src/backend/utils/mb/mbutils.c index e91c8a2a58..89845b08c1 100644 --- a/src/backend/utils/mb/mbutils.c +++ b/src/backend/utils/mb/mbutils.c @@ -4,7 +4,7 @@ * (currently mule internal code (mic) is used) * Tatsuo Ishii * - * $PostgreSQL: pgsql/src/backend/utils/mb/mbutils.c,v 1.59 2006/10/04 00:30:02 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/mb/mbutils.c,v 1.60 2006/12/21 16:05:15 petere Exp $ */ #include "postgres.h" @@ -599,7 +599,7 @@ void SetDatabaseEncoding(int encoding) { if (!PG_VALID_BE_ENCODING(encoding)) - elog(ERROR, "invalid database encoding"); + elog(ERROR, "invalid database encoding: %d", encoding); DatabaseEncoding = &pg_enc2name_tbl[encoding]; Assert(DatabaseEncoding->encoding == encoding); diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h index 40c0fb0ee5..942c4f906e 100644 --- a/src/include/catalog/pg_cast.h +++ b/src/include/catalog/pg_cast.h @@ -10,7 +10,7 @@ * * Copyright (c) 2002-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/catalog/pg_cast.h,v 1.26 2006/03/05 15:58:54 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_cast.h,v 1.27 2006/12/21 16:05:15 petere Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -300,6 +300,8 @@ 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 ( 25 142 2896 e )); /* * Cross-category casts to and from VARCHAR @@ -338,6 +340,8 @@ 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 ( 1043 142 2896 e )); /* * Cross-category casts to and from BPCHAR @@ -377,6 +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 )); /* * Length-coercion functions diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 9dee8f6a3c..974a2db360 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.430 2006/12/06 18:06:47 neilc Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.431 2006/12/21 16:05:15 petere Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -3976,6 +3976,26 @@ DESCR("release shared advisory lock"); DATA(insert OID = 2892 ( pg_advisory_unlock_all PGNSP PGUID 12 f f t f v 0 2278 "" _null_ _null_ _null_ pg_advisory_unlock_all - _null_ )); DESCR("release all advisory locks"); +/* XML support */ +DATA(insert OID = 2893 ( xml_in PGNSP PGUID 12 f f t f i 1 142 "2275" _null_ _null_ _null_ xml_in - _null_ )); +DESCR("I/O"); +DATA(insert OID = 2894 ( xml_out PGNSP PGUID 12 f f t f i 1 2275 "142" _null_ _null_ _null_ xml_out - _null_ )); +DESCR("I/O"); +DATA(insert OID = 2895 ( xmlcomment PGNSP PGUID 12 f f t f i 1 142 "25" _null_ _null_ _null_ xmlcomment - _null_ )); +DESCR("generate an XML comment"); +DATA(insert OID = 2896 ( xmlparse PGNSP PGUID 12 f f t f i 1 142 "25" _null_ _null_ _null_ xmlparse - _null_ )); +DESCR("perform a non-validating parse of a character string to produce an XML value"); +DATA(insert OID = 2897 ( xmlparse PGNSP PGUID 12 f f t f i 3 142 "25 16 16" _null_ _null_ _null_ xmlparse - _null_ )); +DESCR("perform a non-validating parse of a character string to produce an XML value"); +DATA(insert OID = 2898 ( xmlpi PGNSP PGUID 12 f f t f i 1 142 "19" _null_ _null_ _null_ xmlpi - _null_ )); +DESCR("generate an XML processing instruction"); +DATA(insert OID = 2899 ( xmlpi PGNSP PGUID 12 f f t f i 2 142 "19 25" _null_ _null_ _null_ xmlpi - _null_ )); +DESCR("generate an XML processing instruction"); +DATA(insert OID = 2900 ( xmlroot PGNSP PGUID 12 f f f f i 3 142 "142 25 16" _null_ _null_ _null_ xmlroot - _null_ )); +DESCR("create an XML value by modifying the properties of the XML root information item of another XML value"); +DATA(insert OID = 2901 ( xmlvalidate PGNSP PGUID 12 f f t f i 2 16 "142 25" _null_ _null_ _null_ xmlvalidate - _null_ )); +DESCR("validate an XML value"); + /* * Symbolic values for provolatile column: these indicate whether the result * of a function is dependent *only* on the values of its explicit arguments, diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 488ff66bed..cb0c0e8574 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_type.h,v 1.172 2006/10/04 00:30:08 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_type.h,v 1.173 2006/12/21 16:05:15 petere Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -316,6 +316,10 @@ DATA(insert OID = 83 ( pg_class PGNSP PGUID -1 f c t \054 1259 0 record_in reco #define PG_CLASS_RELTYPE_OID 83 /* OIDS 100 - 199 */ +DATA(insert OID = 142 ( xml PGNSP PGUID -1 f b t \054 0 0 xml_in xml_out - - - i x f 0 -1 0 _null_ _null_ )); +DESCR("XML content"); +#define XMLOID 142 +DATA(insert OID = 143 ( _xml PGNSP PGUID -1 f b t \054 0 142 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ )); /* OIDS 200 - 299 */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 273d5c3326..1db920aa0c 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.162 2006/12/04 02:06:55 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.163 2006/12/21 16:05:16 petere Exp $ * *------------------------------------------------------------------------- */ @@ -723,6 +723,22 @@ typedef struct NullTestState TupleDesc argdesc; /* tupdesc for most recent input */ } NullTestState; +/* ---------------- + * XmlExprState node + * ---------------- + */ +typedef struct XmlExprState +{ + ExprState xprstate; + XmlExprOp op; + char *name; + List *named_args; + List *args; + Oid *named_args_tcache; + char **named_args_ncache; + Oid arg_typeout; +} XmlExprState; + /* ---------------- * CoerceToDomainState node * ---------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index c7abfba91a..eb6ba18ada 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.188 2006/09/28 20:51:42 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.189 2006/12/21 16:05:16 petere Exp $ * *------------------------------------------------------------------------- */ @@ -140,6 +140,7 @@ typedef enum NodeTag T_RangeTblRef, T_JoinExpr, T_FromExpr, + T_XmlExpr, /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) @@ -168,6 +169,7 @@ typedef enum NodeTag T_NullTestState, T_CoerceToDomainState, T_DomainConstraintState, + T_XmlExprState, /* * TAGS FOR PLANNER NODES (relation.h) diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 6c5e761275..9654181bc3 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.118 2006/12/10 22:13:27 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.119 2006/12/21 16:05:16 petere Exp $ * *------------------------------------------------------------------------- */ @@ -765,6 +765,26 @@ typedef struct BooleanTest BoolTestType booltesttype; /* test type */ } BooleanTest; +/* + * XmlExpr - holder for SQL/XML functions XMLCONCAT, + * XMLELEMENT, XMLFOREST + */ +typedef enum XmlExprOp +{ + IS_XMLCONCAT, + IS_XMLELEMENT, + IS_XMLFOREST, +} XmlExprOp; + +typedef struct XmlExpr +{ + Expr xpr; + XmlExprOp op; /* xml expression type */ + char *name; /* element name */ + List *named_args; + List *args; +} XmlExpr; + /* * CoerceToDomain * diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index e29a64d48f..9c077095e9 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_coerce.h,v 1.66 2006/10/04 00:30:09 momjian Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_coerce.h,v 1.67 2006/12/21 16:05:16 petere Exp $ * *------------------------------------------------------------------------- */ @@ -59,6 +59,8 @@ extern Node *coerce_to_integer(ParseState *pstate, Node *node, const char *constructName); extern Node *coerce_to_bigint(ParseState *pstate, Node *node, const char *constructName); +extern Node *coerce_to_xml(ParseState *pstate, Node *node, + const char *constructName); extern Oid select_common_type(List *typeids, const char *context); extern Node *coerce_to_common_type(ParseState *pstate, Node *node, diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 7c1ac6b69b..a75f1dccfa 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -251,6 +251,9 @@ /* Define to 1 if you have the `wldap32' library (-lwldap32). */ #undef HAVE_LIBWLDAP32 +/* Define to 1 if you have the `xml2' library (-lxml2). */ +#undef HAVE_LIBXML2 + /* Define to 1 if you have the `z' library (-lz). */ #undef HAVE_LIBZ @@ -627,6 +630,9 @@ /* Define to 1 to build with LDAP support. (--with-ldap) */ #undef USE_LDAP +/* Define to 1 to build with XML support. (--with-libxml) */ +#undef USE_LIBXML + /* Define to select named POSIX semaphores. */ #undef USE_NAMED_POSIX_SEMAPHORES diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h index 8ba809d2ee..9346644774 100644 --- a/src/include/utils/errcodes.h +++ b/src/include/utils/errcodes.h @@ -11,7 +11,7 @@ * * Copyright (c) 2003-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.20 2006/06/16 23:29:26 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.21 2006/12/21 16:05:16 petere Exp $ * *------------------------------------------------------------------------- */ @@ -148,6 +148,10 @@ #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_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') +#define ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION MAKE_SQLSTATE('2', '2', '0', '0', 'T') /* Class 23 - Integrity Constraint Violation */ #define ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION MAKE_SQLSTATE('2','3', '0','0','0') diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h new file mode 100644 index 0000000000..89323301d3 --- /dev/null +++ b/src/include/utils/xml.h @@ -0,0 +1,37 @@ +/*------------------------------------------------------------------------- + * + * xml.h + * Declarations for XML data type support. + * + * + * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.1 2006/12/21 16:05:16 petere Exp $ + * + *------------------------------------------------------------------------- + */ + +#ifndef XML_H +#define XML_H + +#include "fmgr.h" + +typedef struct varlena xmltype; + +#define DatumGetXmlP(X) ((xmltype *) PG_DETOAST_DATUM(X)) + +#define PG_GETARG_XML_P(n) DatumGetXmlP(PG_GETARG_DATUM(n)) +#define PG_RETURN_XML_P(x) PG_RETURN_POINTER(x) + +extern Datum xml_in(PG_FUNCTION_ARGS); +extern Datum xml_out(PG_FUNCTION_ARGS); +extern Datum xmlcomment(PG_FUNCTION_ARGS); +extern Datum xmlparse(PG_FUNCTION_ARGS); +extern Datum xmlpi(PG_FUNCTION_ARGS); +extern Datum xmlroot(PG_FUNCTION_ARGS); +extern Datum xmlvalidate(PG_FUNCTION_ARGS); + +extern char *map_sql_identifier_to_xml_name(unsigned char *ident, bool fully_escaped); + +#endif /* XML_H */ diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 0be263e6d8..e455538d69 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -73,7 +73,7 @@ WHERE p1.oid != p2.oid AND SELECT p1.oid, p1.proname, p2.oid, p2.proname FROM pg_proc AS p1, pg_proc AS p2 WHERE p1.oid < p2.oid AND - p1.prosrc = p2.prosrc AND + p1.prosrc = p2.prosrc AND p1.prosrc NOT IN ('xmlparse', 'xmlpi') AND p1.prolang = 12 AND p2.prolang = 12 AND (p1.proisagg = false OR p2.proisagg = false) AND (p1.prolang != p2.prolang OR @@ -285,6 +285,8 @@ 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 @@ -297,7 +299,10 @@ WHERE c.castfunc = 0 AND 25 | 1042 | 0 | i 1043 | 1042 | 0 | i 650 | 869 | 0 | i -(3 rows) + 142 | 25 | 0 | e + 142 | 1043 | 0 | e + 142 | 1042 | 0 | e +(6 rows) -- **************** pg_operator **************** -- Look for illegal values in pg_operator fields. diff --git a/src/test/regress/expected/prepare.out b/src/test/regress/expected/prepare.out index 902acba4c2..73d38c1ca1 100644 --- a/src/test/regress/expected/prepare.out +++ b/src/test/regress/expected/prepare.out @@ -160,7 +160,7 @@ SELECT name, statement, parameter_types FROM pg_prepared_statements : \x09ten = $3::bigint OR true = $4 OR oid = $5 OR odd = $6::int); q5 | PREPARE q5(int, text) AS | {integer,text} : \x09SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2; - q6 | PREPARE q6 AS | {integer,name} + q6 | PREPARE q6 AS | {integer,"\"name\""} : SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2; q7 | PREPARE q7(unknown) AS | {path} : SELECT * FROM road WHERE thepath = $1; diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 9c0f055edc..d5a3dc09f1 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1276,38 +1276,38 @@ drop table cchild; SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schema' ORDER BY viewname; viewname | definition --------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - iexit | SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath); - pg_cursors | SELECT c.name, c."statement", c.is_holdable, c.is_binary, c.is_scrollable, c.creation_time FROM pg_cursor() c(name text, "statement" text, is_holdable boolean, is_binary boolean, is_scrollable boolean, creation_time timestamp with time zone); + iexit | SELECT ih."name", ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath); + pg_cursors | SELECT c."name", c."statement", c.is_holdable, c.is_binary, c.is_scrollable, c.creation_time FROM pg_cursor() c("name" text, "statement" text, is_holdable boolean, is_binary boolean, is_scrollable boolean, creation_time timestamp with time zone); pg_group | SELECT pg_authid.rolname AS groname, pg_authid.oid AS grosysid, ARRAY(SELECT pg_auth_members.member FROM pg_auth_members WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid WHERE (NOT pg_authid.rolcanlogin); pg_indexes | SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, t.spcname AS "tablespace", pg_get_indexdef(i.oid) AS indexdef FROM ((((pg_index x JOIN pg_class c ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace))) WHERE ((c.relkind = 'r'::"char") AND (i.relkind = 'i'::"char")); pg_locks | SELECT l.locktype, l."database", l.relation, l.page, l.tuple, l.transactionid, l.classid, l.objid, l.objsubid, l."transaction", l.pid, l."mode", l."granted" FROM pg_lock_status() l(locktype text, "database" oid, relation oid, page integer, tuple smallint, transactionid xid, classid oid, objid oid, objsubid smallint, "transaction" xid, pid integer, "mode" text, "granted" boolean); - pg_prepared_statements | SELECT p.name, p."statement", p.prepare_time, p.parameter_types, p.from_sql FROM pg_prepared_statement() p(name text, "statement" text, prepare_time timestamp with time zone, parameter_types regtype[], from_sql boolean); + pg_prepared_statements | SELECT p."name", p."statement", p.prepare_time, p.parameter_types, p.from_sql FROM pg_prepared_statement() p("name" text, "statement" text, prepare_time timestamp with time zone, parameter_types regtype[], from_sql boolean); pg_prepared_xacts | SELECT p."transaction", p.gid, p."prepared", u.rolname AS "owner", d.datname AS "database" FROM ((pg_prepared_xact() p("transaction" xid, gid text, "prepared" timestamp with time zone, ownerid oid, dbid oid) LEFT JOIN pg_authid u ON ((p.ownerid = u.oid))) LEFT JOIN pg_database d ON ((p.dbid = d.oid))); pg_roles | SELECT pg_authid.rolname, pg_authid.rolsuper, pg_authid.rolinherit, pg_authid.rolcreaterole, pg_authid.rolcreatedb, pg_authid.rolcatupdate, pg_authid.rolcanlogin, pg_authid.rolconnlimit, '********'::text AS rolpassword, pg_authid.rolvaliduntil, pg_authid.rolconfig, pg_authid.oid FROM pg_authid; - pg_rules | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid) AS definition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (r.rulename <> '_RETURN'::name); - pg_settings | SELECT a.name, a.setting, a.unit, a.category, a.short_desc, a.extra_desc, a.context, a.vartype, a.source, a.min_val, a.max_val FROM pg_show_all_settings() a(name text, setting text, unit text, category text, short_desc text, extra_desc text, context text, vartype text, source text, min_val text, max_val text); + pg_rules | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid) AS definition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (r.rulename <> '_RETURN'::"name"); + pg_settings | SELECT a."name", a.setting, a.unit, a.category, a.short_desc, a.extra_desc, a.context, a.vartype, a.source, a.min_val, a.max_val FROM pg_show_all_settings() a("name" text, setting text, unit text, category text, short_desc text, extra_desc text, context text, vartype text, source text, min_val text, max_val text); pg_shadow | SELECT pg_authid.rolname AS usename, pg_authid.oid AS usesysid, pg_authid.rolcreatedb AS usecreatedb, pg_authid.rolsuper AS usesuper, pg_authid.rolcatupdate AS usecatupd, pg_authid.rolpassword AS passwd, (pg_authid.rolvaliduntil)::abstime AS valuntil, pg_authid.rolconfig AS useconfig FROM pg_authid WHERE pg_authid.rolcanlogin; pg_stat_activity | SELECT d.oid AS datid, d.datname, pg_stat_get_backend_pid(s.backendid) AS procpid, pg_stat_get_backend_userid(s.backendid) AS usesysid, u.rolname AS usename, pg_stat_get_backend_activity(s.backendid) AS current_query, pg_stat_get_backend_waiting(s.backendid) AS waiting, pg_stat_get_backend_txn_start(s.backendid) AS txn_start, pg_stat_get_backend_activity_start(s.backendid) AS query_start, pg_stat_get_backend_start(s.backendid) AS backend_start, pg_stat_get_backend_client_addr(s.backendid) AS client_addr, pg_stat_get_backend_client_port(s.backendid) AS client_port FROM pg_database d, (SELECT pg_stat_get_backend_idset() AS backendid) s, pg_authid u WHERE ((pg_stat_get_backend_dbid(s.backendid) = d.oid) AND (pg_stat_get_backend_userid(s.backendid) = u.oid)); pg_stat_all_indexes | SELECT c.oid AS relid, i.oid AS indexrelid, n.nspname AS schemaname, c.relname, i.relname AS indexrelname, pg_stat_get_numscans(i.oid) AS idx_scan, pg_stat_get_tuples_returned(i.oid) AS idx_tup_read, pg_stat_get_tuples_fetched(i.oid) AS idx_tup_fetch FROM (((pg_class c JOIN pg_index x ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char"])); pg_stat_all_tables | SELECT c.oid AS relid, n.nspname AS schemaname, c.relname, pg_stat_get_numscans(c.oid) AS seq_scan, pg_stat_get_tuples_returned(c.oid) AS seq_tup_read, (sum(pg_stat_get_numscans(i.indexrelid)))::bigint AS idx_scan, ((sum(pg_stat_get_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tuples_fetched(c.oid)) AS idx_tup_fetch, pg_stat_get_tuples_inserted(c.oid) AS n_tup_ins, pg_stat_get_tuples_updated(c.oid) AS n_tup_upd, pg_stat_get_tuples_deleted(c.oid) AS n_tup_del, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze FROM ((pg_class c LEFT JOIN pg_index i ON ((c.oid = i.indrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char"])) GROUP BY c.oid, n.nspname, c.relname; pg_stat_database | SELECT d.oid AS datid, d.datname, pg_stat_get_db_numbackends(d.oid) AS numbackends, pg_stat_get_db_xact_commit(d.oid) AS xact_commit, pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback, (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read, pg_stat_get_db_blocks_hit(d.oid) AS blks_hit FROM pg_database d; - pg_stat_sys_indexes | SELECT pg_stat_all_indexes.relid, pg_stat_all_indexes.indexrelid, pg_stat_all_indexes.schemaname, pg_stat_all_indexes.relname, pg_stat_all_indexes.indexrelname, pg_stat_all_indexes.idx_scan, pg_stat_all_indexes.idx_tup_read, pg_stat_all_indexes.idx_tup_fetch FROM pg_stat_all_indexes WHERE (pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'pg_toast'::name, 'information_schema'::name])); - pg_stat_sys_tables | SELECT pg_stat_all_tables.relid, pg_stat_all_tables.schemaname, pg_stat_all_tables.relname, pg_stat_all_tables.seq_scan, pg_stat_all_tables.seq_tup_read, pg_stat_all_tables.idx_scan, pg_stat_all_tables.idx_tup_fetch, pg_stat_all_tables.n_tup_ins, pg_stat_all_tables.n_tup_upd, pg_stat_all_tables.n_tup_del, pg_stat_all_tables.last_vacuum, pg_stat_all_tables.last_autovacuum, pg_stat_all_tables.last_analyze, pg_stat_all_tables.last_autoanalyze FROM pg_stat_all_tables WHERE (pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'pg_toast'::name, 'information_schema'::name])); - pg_stat_user_indexes | SELECT pg_stat_all_indexes.relid, pg_stat_all_indexes.indexrelid, pg_stat_all_indexes.schemaname, pg_stat_all_indexes.relname, pg_stat_all_indexes.indexrelname, pg_stat_all_indexes.idx_scan, pg_stat_all_indexes.idx_tup_read, pg_stat_all_indexes.idx_tup_fetch FROM pg_stat_all_indexes WHERE (pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'pg_toast'::name, 'information_schema'::name])); - pg_stat_user_tables | SELECT pg_stat_all_tables.relid, pg_stat_all_tables.schemaname, pg_stat_all_tables.relname, pg_stat_all_tables.seq_scan, pg_stat_all_tables.seq_tup_read, pg_stat_all_tables.idx_scan, pg_stat_all_tables.idx_tup_fetch, pg_stat_all_tables.n_tup_ins, pg_stat_all_tables.n_tup_upd, pg_stat_all_tables.n_tup_del, pg_stat_all_tables.last_vacuum, pg_stat_all_tables.last_autovacuum, pg_stat_all_tables.last_analyze, pg_stat_all_tables.last_autoanalyze FROM pg_stat_all_tables WHERE (pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'pg_toast'::name, 'information_schema'::name])); + pg_stat_sys_indexes | SELECT pg_stat_all_indexes.relid, pg_stat_all_indexes.indexrelid, pg_stat_all_indexes.schemaname, pg_stat_all_indexes.relname, pg_stat_all_indexes.indexrelname, pg_stat_all_indexes.idx_scan, pg_stat_all_indexes.idx_tup_read, pg_stat_all_indexes.idx_tup_fetch FROM pg_stat_all_indexes WHERE (pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::"name", 'pg_toast'::"name", 'information_schema'::"name"])); + pg_stat_sys_tables | SELECT pg_stat_all_tables.relid, pg_stat_all_tables.schemaname, pg_stat_all_tables.relname, pg_stat_all_tables.seq_scan, pg_stat_all_tables.seq_tup_read, pg_stat_all_tables.idx_scan, pg_stat_all_tables.idx_tup_fetch, pg_stat_all_tables.n_tup_ins, pg_stat_all_tables.n_tup_upd, pg_stat_all_tables.n_tup_del, pg_stat_all_tables.last_vacuum, pg_stat_all_tables.last_autovacuum, pg_stat_all_tables.last_analyze, pg_stat_all_tables.last_autoanalyze FROM pg_stat_all_tables WHERE (pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::"name", 'pg_toast'::"name", 'information_schema'::"name"])); + pg_stat_user_indexes | SELECT pg_stat_all_indexes.relid, pg_stat_all_indexes.indexrelid, pg_stat_all_indexes.schemaname, pg_stat_all_indexes.relname, pg_stat_all_indexes.indexrelname, pg_stat_all_indexes.idx_scan, pg_stat_all_indexes.idx_tup_read, pg_stat_all_indexes.idx_tup_fetch FROM pg_stat_all_indexes WHERE (pg_stat_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::"name", 'pg_toast'::"name", 'information_schema'::"name"])); + pg_stat_user_tables | SELECT pg_stat_all_tables.relid, pg_stat_all_tables.schemaname, pg_stat_all_tables.relname, pg_stat_all_tables.seq_scan, pg_stat_all_tables.seq_tup_read, pg_stat_all_tables.idx_scan, pg_stat_all_tables.idx_tup_fetch, pg_stat_all_tables.n_tup_ins, pg_stat_all_tables.n_tup_upd, pg_stat_all_tables.n_tup_del, pg_stat_all_tables.last_vacuum, pg_stat_all_tables.last_autovacuum, pg_stat_all_tables.last_analyze, pg_stat_all_tables.last_autoanalyze FROM pg_stat_all_tables WHERE (pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::"name", 'pg_toast'::"name", 'information_schema'::"name"])); pg_statio_all_indexes | SELECT c.oid AS relid, i.oid AS indexrelid, n.nspname AS schemaname, c.relname, i.relname AS indexrelname, (pg_stat_get_blocks_fetched(i.oid) - pg_stat_get_blocks_hit(i.oid)) AS idx_blks_read, pg_stat_get_blocks_hit(i.oid) AS idx_blks_hit FROM (((pg_class c JOIN pg_index x ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char"])); pg_statio_all_sequences | SELECT c.oid AS relid, n.nspname AS schemaname, c.relname, (pg_stat_get_blocks_fetched(c.oid) - pg_stat_get_blocks_hit(c.oid)) AS blks_read, pg_stat_get_blocks_hit(c.oid) AS blks_hit FROM (pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = 'S'::"char"); pg_statio_all_tables | SELECT c.oid AS relid, n.nspname AS schemaname, c.relname, (pg_stat_get_blocks_fetched(c.oid) - pg_stat_get_blocks_hit(c.oid)) AS heap_blks_read, pg_stat_get_blocks_hit(c.oid) AS heap_blks_hit, (sum((pg_stat_get_blocks_fetched(i.indexrelid) - pg_stat_get_blocks_hit(i.indexrelid))))::bigint AS idx_blks_read, (sum(pg_stat_get_blocks_hit(i.indexrelid)))::bigint AS idx_blks_hit, (pg_stat_get_blocks_fetched(t.oid) - pg_stat_get_blocks_hit(t.oid)) AS toast_blks_read, pg_stat_get_blocks_hit(t.oid) AS toast_blks_hit, (pg_stat_get_blocks_fetched(x.oid) - pg_stat_get_blocks_hit(x.oid)) AS tidx_blks_read, pg_stat_get_blocks_hit(x.oid) AS tidx_blks_hit FROM ((((pg_class c LEFT JOIN pg_index i ON ((c.oid = i.indrelid))) LEFT JOIN pg_class t ON ((c.reltoastrelid = t.oid))) LEFT JOIN pg_class x ON ((t.reltoastidxid = x.oid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char"])) GROUP BY c.oid, n.nspname, c.relname, t.oid, x.oid; - pg_statio_sys_indexes | SELECT pg_statio_all_indexes.relid, pg_statio_all_indexes.indexrelid, pg_statio_all_indexes.schemaname, pg_statio_all_indexes.relname, pg_statio_all_indexes.indexrelname, pg_statio_all_indexes.idx_blks_read, pg_statio_all_indexes.idx_blks_hit FROM pg_statio_all_indexes WHERE (pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'pg_toast'::name, 'information_schema'::name])); - pg_statio_sys_sequences | SELECT pg_statio_all_sequences.relid, pg_statio_all_sequences.schemaname, pg_statio_all_sequences.relname, pg_statio_all_sequences.blks_read, pg_statio_all_sequences.blks_hit FROM pg_statio_all_sequences WHERE (pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::name, 'pg_toast'::name, 'information_schema'::name])); - pg_statio_sys_tables | SELECT pg_statio_all_tables.relid, pg_statio_all_tables.schemaname, pg_statio_all_tables.relname, pg_statio_all_tables.heap_blks_read, pg_statio_all_tables.heap_blks_hit, pg_statio_all_tables.idx_blks_read, pg_statio_all_tables.idx_blks_hit, pg_statio_all_tables.toast_blks_read, pg_statio_all_tables.toast_blks_hit, pg_statio_all_tables.tidx_blks_read, pg_statio_all_tables.tidx_blks_hit FROM pg_statio_all_tables WHERE (pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'pg_toast'::name, 'information_schema'::name])); - pg_statio_user_indexes | SELECT pg_statio_all_indexes.relid, pg_statio_all_indexes.indexrelid, pg_statio_all_indexes.schemaname, pg_statio_all_indexes.relname, pg_statio_all_indexes.indexrelname, pg_statio_all_indexes.idx_blks_read, pg_statio_all_indexes.idx_blks_hit FROM pg_statio_all_indexes WHERE (pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::name, 'pg_toast'::name, 'information_schema'::name])); - pg_statio_user_sequences | SELECT pg_statio_all_sequences.relid, pg_statio_all_sequences.schemaname, pg_statio_all_sequences.relname, pg_statio_all_sequences.blks_read, pg_statio_all_sequences.blks_hit FROM pg_statio_all_sequences WHERE (pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::name, 'pg_toast'::name, 'information_schema'::name])); - pg_statio_user_tables | SELECT pg_statio_all_tables.relid, pg_statio_all_tables.schemaname, pg_statio_all_tables.relname, pg_statio_all_tables.heap_blks_read, pg_statio_all_tables.heap_blks_hit, pg_statio_all_tables.idx_blks_read, pg_statio_all_tables.idx_blks_hit, pg_statio_all_tables.toast_blks_read, pg_statio_all_tables.toast_blks_hit, pg_statio_all_tables.tidx_blks_read, pg_statio_all_tables.tidx_blks_hit FROM pg_statio_all_tables WHERE (pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'pg_toast'::name, 'information_schema'::name])); + pg_statio_sys_indexes | SELECT pg_statio_all_indexes.relid, pg_statio_all_indexes.indexrelid, pg_statio_all_indexes.schemaname, pg_statio_all_indexes.relname, pg_statio_all_indexes.indexrelname, pg_statio_all_indexes.idx_blks_read, pg_statio_all_indexes.idx_blks_hit FROM pg_statio_all_indexes WHERE (pg_statio_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::"name", 'pg_toast'::"name", 'information_schema'::"name"])); + pg_statio_sys_sequences | SELECT pg_statio_all_sequences.relid, pg_statio_all_sequences.schemaname, pg_statio_all_sequences.relname, pg_statio_all_sequences.blks_read, pg_statio_all_sequences.blks_hit FROM pg_statio_all_sequences WHERE (pg_statio_all_sequences.schemaname = ANY (ARRAY['pg_catalog'::"name", 'pg_toast'::"name", 'information_schema'::"name"])); + pg_statio_sys_tables | SELECT pg_statio_all_tables.relid, pg_statio_all_tables.schemaname, pg_statio_all_tables.relname, pg_statio_all_tables.heap_blks_read, pg_statio_all_tables.heap_blks_hit, pg_statio_all_tables.idx_blks_read, pg_statio_all_tables.idx_blks_hit, pg_statio_all_tables.toast_blks_read, pg_statio_all_tables.toast_blks_hit, pg_statio_all_tables.tidx_blks_read, pg_statio_all_tables.tidx_blks_hit FROM pg_statio_all_tables WHERE (pg_statio_all_tables.schemaname = ANY (ARRAY['pg_catalog'::"name", 'pg_toast'::"name", 'information_schema'::"name"])); + pg_statio_user_indexes | SELECT pg_statio_all_indexes.relid, pg_statio_all_indexes.indexrelid, pg_statio_all_indexes.schemaname, pg_statio_all_indexes.relname, pg_statio_all_indexes.indexrelname, pg_statio_all_indexes.idx_blks_read, pg_statio_all_indexes.idx_blks_hit FROM pg_statio_all_indexes WHERE (pg_statio_all_indexes.schemaname <> ALL (ARRAY['pg_catalog'::"name", 'pg_toast'::"name", 'information_schema'::"name"])); + pg_statio_user_sequences | SELECT pg_statio_all_sequences.relid, pg_statio_all_sequences.schemaname, pg_statio_all_sequences.relname, pg_statio_all_sequences.blks_read, pg_statio_all_sequences.blks_hit FROM pg_statio_all_sequences WHERE (pg_statio_all_sequences.schemaname <> ALL (ARRAY['pg_catalog'::"name", 'pg_toast'::"name", 'information_schema'::"name"])); + pg_statio_user_tables | SELECT pg_statio_all_tables.relid, pg_statio_all_tables.schemaname, pg_statio_all_tables.relname, pg_statio_all_tables.heap_blks_read, pg_statio_all_tables.heap_blks_hit, pg_statio_all_tables.idx_blks_read, pg_statio_all_tables.idx_blks_hit, pg_statio_all_tables.toast_blks_read, pg_statio_all_tables.toast_blks_hit, pg_statio_all_tables.tidx_blks_read, pg_statio_all_tables.tidx_blks_hit FROM pg_statio_all_tables WHERE (pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::"name", 'pg_toast'::"name", 'information_schema'::"name"])); pg_stats | SELECT n.nspname AS schemaname, c.relname AS tablename, a.attname, s.stanullfrac AS null_frac, s.stawidth AS avg_width, s.stadistinct AS n_distinct, CASE 1 WHEN s.stakind1 THEN s.stavalues1 WHEN s.stakind2 THEN s.stavalues2 WHEN s.stakind3 THEN s.stavalues3 WHEN s.stakind4 THEN s.stavalues4 ELSE NULL::anyarray END AS most_common_vals, CASE 1 WHEN s.stakind1 THEN s.stanumbers1 WHEN s.stakind2 THEN s.stanumbers2 WHEN s.stakind3 THEN s.stanumbers3 WHEN s.stakind4 THEN s.stanumbers4 ELSE NULL::real[] END AS most_common_freqs, CASE 2 WHEN s.stakind1 THEN s.stavalues1 WHEN s.stakind2 THEN s.stavalues2 WHEN s.stakind3 THEN s.stavalues3 WHEN s.stakind4 THEN s.stavalues4 ELSE NULL::anyarray END AS histogram_bounds, CASE 3 WHEN s.stakind1 THEN s.stanumbers1[1] WHEN s.stakind2 THEN s.stanumbers2[1] WHEN s.stakind3 THEN s.stanumbers3[1] WHEN s.stakind4 THEN s.stanumbers4[1] ELSE NULL::real END AS correlation FROM (((pg_statistic s JOIN pg_class c ON ((c.oid = s.starelid))) JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum)))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE has_table_privilege(c.oid, 'select'::text); pg_tables | SELECT n.nspname AS schemaname, c.relname AS tablename, pg_get_userbyid(c.relowner) AS tableowner, t.spcname AS "tablespace", c.relhasindex AS hasindexes, c.relhasrules AS hasrules, (c.reltriggers > 0) AS hastriggers FROM ((pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace))) WHERE (c.relkind = 'r'::"char"); pg_timezone_abbrevs | SELECT pg_timezone_abbrevs.abbrev, pg_timezone_abbrevs.utc_offset, pg_timezone_abbrevs.is_dst FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst); - pg_timezone_names | SELECT pg_timezone_names.name, pg_timezone_names.abbrev, pg_timezone_names.utc_offset, pg_timezone_names.is_dst FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst); + pg_timezone_names | SELECT pg_timezone_names."name", pg_timezone_names.abbrev, pg_timezone_names.utc_offset, pg_timezone_names.is_dst FROM pg_timezone_names() pg_timezone_names("name", abbrev, utc_offset, is_dst); pg_user | SELECT pg_shadow.usename, pg_shadow.usesysid, pg_shadow.usecreatedb, pg_shadow.usesuper, pg_shadow.usecatupd, '********'::text AS passwd, pg_shadow.valuntil, pg_shadow.useconfig FROM pg_shadow; pg_views | SELECT n.nspname AS schemaname, c.relname AS viewname, pg_get_userbyid(c.relowner) AS viewowner, pg_get_viewdef(c.oid) AS definition FROM (pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = 'v'::"char"); rtest_v1 | SELECT rtest_t1.a, rtest_t1.b FROM rtest_t1; @@ -1322,16 +1322,16 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem shoelace | SELECT s.sl_name, s.sl_avail, s.sl_color, s.sl_len, s.sl_unit, (s.sl_len * u.un_fact) AS sl_len_cm FROM shoelace_data s, unit u WHERE (s.sl_unit = u.un_name); shoelace_candelete | SELECT shoelace_obsolete.sl_name, shoelace_obsolete.sl_avail, shoelace_obsolete.sl_color, shoelace_obsolete.sl_len, shoelace_obsolete.sl_unit, shoelace_obsolete.sl_len_cm FROM shoelace_obsolete WHERE (shoelace_obsolete.sl_avail = 0); shoelace_obsolete | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color)))); - street | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath); - toyemp | SELECT emp.name, emp.age, emp."location", (12 * emp.salary) AS annualsal FROM emp; + street | SELECT r."name", r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath); + toyemp | SELECT emp."name", emp.age, emp."location", (12 * emp.salary) AS annualsal FROM emp; (48 rows) SELECT tablename, rulename, definition FROM pg_rules ORDER BY tablename, rulename; - tablename | rulename | definition ----------------+-----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + tablename | rulename | definition +---------------+-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- pg_settings | pg_settings_n | CREATE RULE pg_settings_n AS ON UPDATE TO pg_settings DO INSTEAD NOTHING; - pg_settings | pg_settings_u | CREATE RULE pg_settings_u AS ON UPDATE TO pg_settings WHERE (new.name = old.name) DO SELECT set_config(old.name, new.setting, false) AS set_config; + pg_settings | pg_settings_u | CREATE RULE pg_settings_u AS ON UPDATE TO pg_settings WHERE (new."name" = old."name") DO SELECT set_config(old."name", new.setting, false) AS set_config; rtest_emp | rtest_emp_del | CREATE RULE rtest_emp_del AS ON DELETE TO rtest_emp DO INSERT INTO rtest_emplog (ename, who, "action", newsal, oldsal) VALUES (old.ename, "current_user"(), 'fired'::bpchar, '$0.00'::money, old.salary); rtest_emp | rtest_emp_ins | CREATE RULE rtest_emp_ins AS ON INSERT TO rtest_emp DO INSERT INTO rtest_emplog (ename, who, "action", newsal, oldsal) VALUES (new.ename, "current_user"(), 'hired'::bpchar, new.salary, '$0.00'::money); rtest_emp | rtest_emp_upd | CREATE RULE rtest_emp_upd AS ON UPDATE TO rtest_emp WHERE (new.salary <> old.salary) DO INSERT INTO rtest_emplog (ename, who, "action", newsal, oldsal) VALUES (new.ename, "current_user"(), 'honored'::bpchar, new.salary, old.salary); @@ -1357,7 +1357,7 @@ SELECT tablename, rulename, definition FROM pg_rules shoelace | shoelace_del | CREATE RULE shoelace_del AS ON DELETE TO shoelace DO INSTEAD DELETE FROM shoelace_data WHERE (shoelace_data.sl_name = old.sl_name); shoelace | shoelace_ins | CREATE RULE shoelace_ins AS ON INSERT TO shoelace DO INSTEAD INSERT INTO shoelace_data (sl_name, sl_avail, sl_color, sl_len, sl_unit) VALUES (new.sl_name, new.sl_avail, new.sl_color, new.sl_len, new.sl_unit); shoelace | shoelace_upd | CREATE RULE shoelace_upd AS ON UPDATE TO shoelace DO INSTEAD UPDATE shoelace_data SET sl_name = new.sl_name, sl_avail = new.sl_avail, sl_color = new.sl_color, sl_len = new.sl_len, sl_unit = new.sl_unit WHERE (shoelace_data.sl_name = old.sl_name); - shoelace_data | log_shoelace | CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data WHERE (new.sl_avail <> old.sl_avail) DO INSERT INTO shoelace_log (sl_name, sl_avail, log_who, log_when) VALUES (new.sl_name, new.sl_avail, 'Al Bundy'::name, 'Thu Jan 01 00:00:00 1970'::timestamp without time zone); + shoelace_data | log_shoelace | CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data WHERE (new.sl_avail <> old.sl_avail) DO INSERT INTO shoelace_log (sl_name, sl_avail, log_who, log_when) VALUES (new.sl_name, new.sl_avail, 'Al Bundy'::"name", 'Thu Jan 01 00:00:00 1970'::timestamp without time zone); shoelace_ok | shoelace_ok_ins | CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok DO INSTEAD UPDATE shoelace SET sl_avail = (shoelace.sl_avail + new.ok_quant) WHERE (shoelace.sl_name = new.ok_name); (29 rows) diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out new file mode 100644 index 0000000000..20a2d4fa02 --- /dev/null +++ b/src/test/regress/expected/xml.out @@ -0,0 +1,145 @@ +CREATE TABLE xmltest ( + id int, + data xml +); +INSERT INTO xmltest VALUES (1, '<value>one</value>'); +INSERT INTO xmltest VALUES (2, '<value>two</value>'); +INSERT INTO xmltest VALUES (3, '<wrong'); +ERROR: could not parse XML data +DETAIL: Expected '>' +SELECT * FROM xmltest; + id | data +----+-------------------- + 1 | <value>one</value> + 2 | <value>two</value> +(2 rows) + +SELECT xmlcomment('test'); + xmlcomment +------------- + <!--test--> +(1 row) + +SELECT xmlcomment('-test'); + xmlcomment +-------------- + <!---test--> +(1 row) + +SELECT xmlcomment('test-'); +ERROR: invalid XML comment +SELECT xmlcomment('--test'); +ERROR: invalid XML comment +SELECT xmlcomment('te st'); + xmlcomment +-------------- + <!--te st--> +(1 row) + +SELECT xmlconcat(xmlcomment('hello'), + xmlelement(NAME qux, 'foo'), + xmlcomment('world')); + xmlconcat +---------------------------------------- + <!--hello--><qux>foo</qux><!--world--> +(1 row) + +SELECT xmlconcat('hello', 'you'); + xmlconcat +----------- + helloyou +(1 row) + +SELECT xmlconcat(1, 2); +ERROR: argument of XMLCONCAT must be type xml, not type integer +SELECT xmlconcat('bad', '<syntax'); +ERROR: could not parse XML data +DETAIL: Expected '>' +SELECT xmlelement(name element, + xmlattributes (1 as one, 'deuce' as two), + 'content'); + xmlelement +------------------------------------------------ + <element one="1" two="deuce">content</element> +(1 row) + +SELECT xmlelement(name element, + xmlattributes ('unnamed and wrong')); +ERROR: unnamed attribute value must be a column reference +SELECT xmlelement(name element, xmlelement(name nested, 'stuff')); + xmlelement +------------------------------------------- + <element><nested>stuff</nested></element> +(1 row) + +SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp; + xmlelement +---------------------------------------------------------------------- + <employee><name>sharon</name><age>25</age><pay>1000</pay></employee> + <employee><name>sam</name><age>30</age><pay>2000</pay></employee> + <employee><name>bill</name><age>20</age><pay>1000</pay></employee> + <employee><name>jeff</name><age>23</age><pay>600</pay></employee> + <employee><name>cim</name><age>30</age><pay>400</pay></employee> + <employee><name>linda</name><age>19</age><pay>100</pay></employee> +(6 rows) + +SELECT xmlelement(name wrong, 37); +ERROR: argument of XMLELEMENT must be type xml, not type integer +SELECT xmlpi(name foo); + xmlpi +--------- + <?foo?> +(1 row) + +SELECT xmlpi(name xmlstuff); +ERROR: invalid XML processing instruction +DETAIL: XML processing instruction target name cannot start with "xml". +SELECT xmlpi(name foo, 'bar'); + xmlpi +------------- + <?foo bar?> +(1 row) + +SELECT xmlpi(name foo, 'in?>valid'); +ERROR: invalid XML processing instruction +DETAIL: XML processing instruction cannot contain "?>". +SELECT xmlroot ( + xmlelement ( + name gazonk, + xmlattributes ( + 'val' AS name, + 1 + 1 AS num + ), + xmlelement ( + NAME qux, + 'foo' + ) + ), + version '1.0', + standalone yes +); + xmlroot +------------------------------------------------------------------------------------------ + <?xml version="1.0" standalone="yes"?><gazonk name="val" num="2"><qux>foo</qux></gazonk> +(1 row) + +SELECT xmlserialize(content data as character varying) FROM xmltest; + data +-------------------- + <value>one</value> + <value>two</value> +(2 rows) + +-- Check mapping SQL identifier to XML name +SELECT xmlpi(name ":::_xml_abc135.%-&_"); + xmlpi +------------------------------------------------- + <?_x003A_::_x005F_xml_abc135._x0025_-_x0026__?> +(1 row) + +SELECT xmlpi(name "123"); + xmlpi +--------------- + <?_x0031_23?> +(1 row) + diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out new file mode 100644 index 0000000000..24d1e18f96 --- /dev/null +++ b/src/test/regress/expected/xml_1.out @@ -0,0 +1,77 @@ +CREATE TABLE xmltest ( + id int, + data xml +); +INSERT INTO xmltest VALUES (1, '<value>one</value>'); +ERROR: no XML support in this installation +INSERT INTO xmltest VALUES (2, '<value>two</value>'); +ERROR: no XML support in this installation +INSERT INTO xmltest VALUES (3, '<wrong'); +ERROR: no XML support in this installation +SELECT * FROM xmltest; + id | data +----+------ +(0 rows) + +SELECT xmlcomment('test'); +ERROR: no XML support in this installation +SELECT xmlcomment('-test'); +ERROR: no XML support in this installation +SELECT xmlcomment('test-'); +ERROR: no XML support in this installation +SELECT xmlcomment('--test'); +ERROR: no XML support in this installation +SELECT xmlcomment('te st'); +ERROR: no XML support in this installation +SELECT xmlconcat(xmlcomment('hello'), + xmlelement(NAME qux, 'foo'), + xmlcomment('world')); +ERROR: no XML support in this installation +SELECT xmlconcat('hello', 'you'); +ERROR: no XML support in this installation +SELECT xmlconcat(1, 2); +ERROR: argument of XMLCONCAT must be type xml, not type integer +SELECT xmlconcat('bad', '<syntax'); +ERROR: no XML support in this installation +SELECT xmlelement(name element, + xmlattributes (1 as one, 'deuce' as two), + 'content'); +ERROR: no XML support in this installation +SELECT xmlelement(name element, + xmlattributes ('unnamed and wrong')); +ERROR: no XML support in this installation +SELECT xmlelement(name element, xmlelement(name nested, 'stuff')); +ERROR: no XML support in this installation +SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp; +ERROR: no XML support in this installation +SELECT xmlelement(name wrong, 37); +ERROR: no XML support in this installation +SELECT xmlpi(name foo); +ERROR: no XML support in this installation +SELECT xmlpi(name xmlstuff); +ERROR: no XML support in this installation +SELECT xmlpi(name foo, 'bar'); +ERROR: no XML support in this installation +SELECT xmlpi(name foo, 'in?>valid'); +ERROR: no XML support in this installation +SELECT xmlroot ( + xmlelement ( + name gazonk, + xmlattributes ( + 'val' AS name, + 1 + 1 AS num + ), + xmlelement ( + NAME qux, + 'foo' + ) + ), + version '1.0', + standalone yes +); +ERROR: no XML support in this installation +-- Check mapping SQL identifier to XML name +SELECT xmlpi(name ":::_xml_abc135.%-&_"); +ERROR: no XML support in this installation +SELECT xmlpi(name "123"); +ERROR: no XML support in this installation diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index f13ea4792a..a8f9ee09b5 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -1,6 +1,6 @@ # ---------- # The first group of parallel test -# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.35 2006/08/30 23:34:22 tgl Exp $ +# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.36 2006/12/21 16:05:16 petere Exp $ # ---------- test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric @@ -75,7 +75,7 @@ test: select_views portals_p2 rules foreign_key cluster dependency guc # The sixth group of parallel test # ---------- # "plpgsql" cannot run concurrently with "rules" -test: limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning +test: limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning xml # run stats by itself because its delay may be insufficient under heavy load test: stats diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 2d44e585d3..1fa94fadc0 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -1,4 +1,4 @@ -# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.33 2006/08/30 23:34:22 tgl Exp $ +# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.34 2006/12/21 16:05:16 petere Exp $ # This should probably be in an order similar to parallel_schedule. test: boolean test: char @@ -102,5 +102,6 @@ test: sequence test: polymorphism test: rowtypes test: returning +test: xml test: stats test: tablespace diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 7fc9915275..5195a290ab 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -76,7 +76,7 @@ WHERE p1.oid != p2.oid AND SELECT p1.oid, p1.proname, p2.oid, p2.proname FROM pg_proc AS p1, pg_proc AS p2 WHERE p1.oid < p2.oid AND - p1.prosrc = p2.prosrc AND + p1.prosrc = p2.prosrc AND p1.prosrc NOT IN ('xmlparse', 'xmlpi') AND p1.prolang = 12 AND p2.prolang = 12 AND (p1.proisagg = false OR p2.proisagg = false) AND (p1.prolang != p2.prolang OR @@ -235,6 +235,9 @@ 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 new file mode 100644 index 0000000000..061f6329f9 --- /dev/null +++ b/src/test/regress/sql/xml.sql @@ -0,0 +1,72 @@ +CREATE TABLE xmltest ( + id int, + data xml +); + +INSERT INTO xmltest VALUES (1, '<value>one</value>'); +INSERT INTO xmltest VALUES (2, '<value>two</value>'); +INSERT INTO xmltest VALUES (3, '<wrong'); + +SELECT * FROM xmltest; + + +SELECT xmlcomment('test'); +SELECT xmlcomment('-test'); +SELECT xmlcomment('test-'); +SELECT xmlcomment('--test'); +SELECT xmlcomment('te st'); + + +SELECT xmlconcat(xmlcomment('hello'), + xmlelement(NAME qux, 'foo'), + xmlcomment('world')); + +SELECT xmlconcat('hello', 'you'); +SELECT xmlconcat(1, 2); +SELECT xmlconcat('bad', '<syntax'); + + +SELECT xmlelement(name element, + xmlattributes (1 as one, 'deuce' as two), + 'content'); + +SELECT xmlelement(name element, + xmlattributes ('unnamed and wrong')); + +SELECT xmlelement(name element, xmlelement(name nested, 'stuff')); + +SELECT xmlelement(name employee, xmlforest(name, age, salary as pay)) FROM emp; + +SELECT xmlelement(name wrong, 37); + + +SELECT xmlpi(name foo); +SELECT xmlpi(name xmlstuff); +SELECT xmlpi(name foo, 'bar'); +SELECT xmlpi(name foo, 'in?>valid'); + + +SELECT xmlroot ( + xmlelement ( + name gazonk, + xmlattributes ( + 'val' AS name, + 1 + 1 AS num + ), + xmlelement ( + NAME qux, + 'foo' + ) + ), + version '1.0', + standalone yes +); + + +SELECT xmlserialize(content data as character varying) FROM xmltest; + + +-- Check mapping SQL identifier to XML name + +SELECT xmlpi(name ":::_xml_abc135.%-&_"); +SELECT xmlpi(name "123"); -- 2.40.0