diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 8052f8b2d76590715ce199eb29c32fd2830a2709..ca804dea21056a0e22d8cb37c3c41a75eb8ad6da 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.206 2007/01/12 21:47:26 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.207 2007/01/14 13:11:53 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2808,6 +2808,25 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext,
 											   standalone));
 			}
 			break;
+
+		case IS_DOCUMENT:
+			{
+				ExprState 	*e;
+
+				/* optional argument is known to be xml */
+				Assert(list_length(xmlExpr->args) == 1);
+
+				e = (ExprState *) linitial(xmlExpr->args);
+				value = ExecEvalExpr(e, econtext, &isnull, NULL);
+				if (isnull)
+					return (Datum) 0;
+				else
+				{
+					*isNull = false;
+					return BoolGetDatum(xml_is_document(DatumGetXmlP(value)));
+				}
+			}
+			break;
 	}
 
 	if (*isNull)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6abe0d6795a28d710fbca063c1e891201b4151cb..db66a69b3ff6ffe00e8dddf2936e268087d150e2 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.573 2007/01/09 02:14:14 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.574 2007/01/14 13:11:53 petere Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -7147,6 +7147,16 @@ a_expr:		c_expr									{ $$ = $1; }
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("UNIQUE predicate is not yet implemented")));
 				}
+			| a_expr IS DOCUMENT_P					%prec IS
+				{
+					$$ = makeXmlExpr(IS_DOCUMENT, NULL, NIL, list_make1($1));
+				}
+			| a_expr IS NOT DOCUMENT_P				%prec IS
+				{
+					$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL,
+											 makeXmlExpr(IS_DOCUMENT, NULL, NIL, list_make1($1)),
+											 @2);
+				}
 		;
 
 /*
@@ -7207,6 +7217,16 @@ b_expr:		c_expr
 				{
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "<>", $1, (Node *) $6, @2);
 				}
+			| b_expr IS DOCUMENT_P					%prec IS
+				{
+					$$ = makeXmlExpr(IS_DOCUMENT, NULL, NIL, list_make1($1));
+				}
+			| b_expr IS NOT DOCUMENT_P				%prec IS
+				{
+					$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL,
+											 makeXmlExpr(IS_DOCUMENT, NULL, NIL, list_make1($1)),
+											 @2);
+				}
 		;
 
 /*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d9e42011d414133fc6ea4bdb5a4b0d94e1753fcb..394a507f2ef11949206debd9fe396919a44bf2d6 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.207 2007/01/12 22:09:49 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.208 2007/01/14 13:11:53 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1483,6 +1483,10 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
 				else
 					newe = coerce_to_boolean(pstate, newe, "XMLROOT");
 				break;
+			case IS_DOCUMENT:
+				newe = coerce_to_specific_type(pstate, newe, XMLOID,
+											   "IS DOCUMENT");
+				break;
 		}
 		newx->args = lappend(newx->args, newe);
 		i++;
@@ -1782,7 +1786,10 @@ exprType(Node *expr)
 			type = ((MinMaxExpr *) expr)->minmaxtype;
 			break;
 		case T_XmlExpr:
-			type = XMLOID;
+			if (((XmlExpr *) expr)->op == IS_DOCUMENT)
+				type = BOOLOID;
+			else
+				type = XMLOID;
 			break;
 		case T_NullIfExpr:
 			type = exprType((Node *) linitial(((NullIfExpr *) expr)->args));
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3a4caa81f8115f99949140dbee11505811a8f231..dea29d1d8aa4995c859037ddfbee8491851ccfb1 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.152 2007/01/05 22:19:34 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.153 2007/01/14 13:11:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1337,6 +1337,9 @@ FigureColnameInternal(Node *node, char **name)
 				case IS_XMLROOT:
 					*name = "xmlroot";
 					return 2;
+				case IS_DOCUMENT:
+					/* nothing */
+					break;
 			} 
 			break;
 		default:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3c217c98edc24c7f88f8f77081c79386d6745e11..be23d938f80f7986da8237b6c164c5092447a9f4 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.241 2007/01/09 02:14:14 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.242 2007/01/14 13:11:54 petere Exp $
  **********************************************************************/
 
 #include "postgres.h"
@@ -3847,6 +3847,8 @@ get_rule_expr(Node *node, deparse_context *context,
 					case IS_XMLROOT:
 						appendStringInfoString(buf, "XMLROOT(");
 						break;
+					case IS_DOCUMENT:
+						break;
 				}
 				if (xexpr->name)
 				{
@@ -3888,6 +3890,7 @@ get_rule_expr(Node *node, deparse_context *context,
 						case IS_XMLELEMENT:
 						case IS_XMLFOREST:
 						case IS_XMLPI:
+						case IS_DOCUMENT:
 							/* no extra decoration needed */
 							get_rule_expr((Node *) xexpr->args, context, true);
 							break;
@@ -3943,7 +3946,10 @@ get_rule_expr(Node *node, deparse_context *context,
 					}
 
 				}
-				appendStringInfoChar(buf, ')');
+				if (xexpr->op == IS_DOCUMENT)
+					appendStringInfoString(buf, " IS DOCUMENT");
+				else
+					appendStringInfoChar(buf, ')');
 			}
 			break;
 
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index da04bee15dea67c8c26e7f7658e04b0d3c0cd159..87cb5b0d64073b7ee65c51b600d6b4dcf82d3e74 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.16 2007/01/12 21:47:26 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.17 2007/01/14 13:11:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -515,6 +515,50 @@ xmlvalidate(PG_FUNCTION_ARGS)
 }
 
 
+bool
+xml_is_document(xmltype *arg)
+{
+#ifdef USE_LIBXML
+	bool		result;
+	xmlDocPtr	doc = NULL;
+	MemoryContext ccxt = CurrentMemoryContext;
+
+	PG_TRY();
+	{
+		doc = xml_parse((text *) arg, true, true);
+		result = true;
+	}
+	PG_CATCH();
+	{
+		ErrorData *errdata;
+		MemoryContext ecxt;
+
+		ecxt = MemoryContextSwitchTo(ccxt);
+		errdata = CopyErrorData();
+		if (errdata->sqlerrcode == ERRCODE_INVALID_XML_DOCUMENT)
+		{
+			FlushErrorState();
+			result = false;
+		}
+		else
+		{
+			MemoryContextSwitchTo(ecxt);
+			PG_RE_THROW();
+		}
+	}
+	PG_END_TRY();
+
+	if (doc)
+		xmlFreeDoc(doc);
+
+	return result;
+#else /* not USE_LIBXML */
+	NO_XML_SUPPORT();
+	return false;
+#endif /* not USE_LIBXML */
+}
+
+
 #ifdef USE_LIBXML
 
 /*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 921ac0d0b4be6476ae2277a58f022604d6bb05c8..cea0cd2f6a5c24ac83e036133821019c2141e87f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.122 2007/01/05 22:19:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.123 2007/01/14 13:11:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -725,7 +725,8 @@ typedef enum XmlExprOp
 	IS_XMLFOREST,				/* XMLFOREST(xml_attributes) */
 	IS_XMLPARSE,				/* XMLPARSE(text, is_doc, preserve_ws) */
 	IS_XMLPI,					/* XMLPI(name [, args]) */
-	IS_XMLROOT					/* XMLROOT(xml, version, standalone) */
+	IS_XMLROOT,					/* XMLROOT(xml, version, standalone) */
+	IS_DOCUMENT					/* xmlval IS DOCUMENT */
 } XmlExprOp;
 
 typedef struct XmlExpr
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index b7b105ef15781e6aba32e8543abf3726ddcd1933..9e576bdecbecefe9eac1effb1b3174cb91747498 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.9 2007/01/12 21:47:27 petere Exp $
+ * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.10 2007/01/14 13:11:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,6 +37,7 @@ extern xmltype *xmlelement(XmlExprState *xmlExpr, ExprContext *econtext);
 extern xmltype *xmlparse(text *data, bool is_doc, bool preserve_whitespace);
 extern xmltype *xmlpi(char *target, text *arg, bool arg_is_null, bool *result_is_null);
 extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
+extern bool xml_is_document(xmltype *arg);
 
 extern char *map_sql_identifier_to_xml_name(char *ident, bool fully_escaped);
 extern char *map_xml_name_to_sql_identifier(char *name);
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 275523727b45bcc0daba9a233b420bb313058126..c33fd8e414a1519b63c90ac074f38da26bccc0ff 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -228,6 +228,33 @@ SELECT xmlserialize(content data as character varying) FROM xmltest;
  <value>two</value>
 (2 rows)
 
+SELECT xml '<foo>bar</foo>' IS DOCUMENT;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT xml '<abc/>' IS NOT DOCUMENT;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT xml 'abc' IS NOT DOCUMENT;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '<>' IS NOT DOCUMENT;
+ERROR:  invalid XML content
+DETAIL:  Element name not found
 -- Check mapping SQL identifier to XML name
 SELECT xmlpi(name ":::_xml_abc135.%-&_");
                       xmlpi                      
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 9ff3959160e00c60a69fc50ad5fbc13ad30b8b8c..4534ae98cc573842c71bd71c7b19ab3565e26a9a 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -107,6 +107,16 @@ SELECT xmlserialize(content data as character varying) FROM xmltest;
 ------
 (0 rows)
 
+SELECT xml '<foo>bar</foo>' IS DOCUMENT;
+ERROR:  no XML support in this installation
+SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
+ERROR:  no XML support in this installation
+SELECT xml '<abc/>' IS NOT DOCUMENT;
+ERROR:  no XML support in this installation
+SELECT xml 'abc' IS NOT DOCUMENT;
+ERROR:  no XML support in this installation
+SELECT '<>' IS NOT DOCUMENT;
+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
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index a22c825129886a9817b15433d65da163492c53d2..4492a62cdb0ce3afa4521ed7531645e224ee000f 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -86,6 +86,13 @@ SELECT xmlroot (
 SELECT xmlserialize(content data as character varying) FROM xmltest;
 
 
+SELECT xml '<foo>bar</foo>' IS DOCUMENT;
+SELECT xml '<foo>bar</foo><bar>foo</bar>' IS DOCUMENT;
+SELECT xml '<abc/>' IS NOT DOCUMENT;
+SELECT xml 'abc' IS NOT DOCUMENT;
+SELECT '<>' IS NOT DOCUMENT;
+
+
 -- Check mapping SQL identifier to XML name
 
 SELECT xmlpi(name ":::_xml_abc135.%-&_");