summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mysql-test/r/xml.result38
-rw-r--r--mysql-test/t/xml.test30
-rw-r--r--sql/item_xmlfunc.cc135
-rw-r--r--sql/item_xmlfunc.h55
4 files changed, 216 insertions, 42 deletions
diff --git a/mysql-test/r/xml.result b/mysql-test/r/xml.result
index 6a4f5ace7d6..05c0db6e3cf 100644
--- a/mysql-test/r/xml.result
+++ b/mysql-test/r/xml.result
@@ -1214,3 +1214,41 @@ DROP TABLE t1;
#
# End of 5.5 tests
#
+#
+# Start of 10.0 tests
+#
+#
+# MDEV-5689 ExtractValue(xml, 'substring(/x,/y)') crashes
+#
+SELECT ExtractValue('<a><b>abc</b><c>2</c><d>1</d></a>','substring(/a/b,..)') AS e;
+e
+
+SELECT ExtractValue('<a><b>abc</b><c>2</c><d>1</d></a>','substring(/a/b,/a/c)') AS e;
+e
+bc
+SELECT ExtractValue('<a><b>abc</b><c>2</c><d>1</d></a>','substring(/a/b,/a/d)') AS e;
+e
+abc
+SELECT ExtractValue('<a><b>abc</b><c>2</c><d>1</d></a>','substring(/a/b,/a/c,/a/d)') AS e;
+e
+b
+SELECT ExtractValue('<a><b>abc</b><c>2</c><d>1</d></a>','substring(/a/b,/a/d,/a/c)') AS e;
+e
+ab
+#
+# MDEV-5709 ExtractValue() with XPath variable references returns wrong result
+#
+CREATE TABLE t1 (c1 INT, c2 VARCHAR(10));
+INSERT INTO t1 VALUES (1,'b1'),(2,'b2');
+SELECT *,IF(@i:=c1,ExtractValue('<a><b>b1</b><b>b2</b></a>','//b[$@i]'),0) AS xpath FROM t1;
+c1 c2 xpath
+1 b1 b1
+2 b2 b2
+SELECT * FROM t1 WHERE c2=IF(@i:=c1,ExtractValue('<a><b>b1</b><b>b2</b></a>','//b[$@i]'),0);
+c1 c2
+1 b1
+2 b2
+DROP TABLE t1;
+#
+# End of 10.0 tests
+#
diff --git a/mysql-test/t/xml.test b/mysql-test/t/xml.test
index e84e3ac247f..bc9a74fd8cd 100644
--- a/mysql-test/t/xml.test
+++ b/mysql-test/t/xml.test
@@ -707,6 +707,36 @@ INSERT INTO t1 VALUES (CONCAT('<a><', REPEAT('b',128),'>b128</',REPEAT('b',128),
SELECT ExtractValue (a, CONCAT('//',REPEAT('c',512))) AS c512 FROM t1;
DROP TABLE t1;
+--horizontal_results
+
--echo #
--echo # End of 5.5 tests
--echo #
+
+--echo #
+--echo # Start of 10.0 tests
+--echo #
+
+
+--echo #
+--echo # MDEV-5689 ExtractValue(xml, 'substring(/x,/y)') crashes
+--echo #
+SELECT ExtractValue('<a><b>abc</b><c>2</c><d>1</d></a>','substring(/a/b,..)') AS e;
+SELECT ExtractValue('<a><b>abc</b><c>2</c><d>1</d></a>','substring(/a/b,/a/c)') AS e;
+SELECT ExtractValue('<a><b>abc</b><c>2</c><d>1</d></a>','substring(/a/b,/a/d)') AS e;
+SELECT ExtractValue('<a><b>abc</b><c>2</c><d>1</d></a>','substring(/a/b,/a/c,/a/d)') AS e;
+SELECT ExtractValue('<a><b>abc</b><c>2</c><d>1</d></a>','substring(/a/b,/a/d,/a/c)') AS e;
+
+--echo #
+--echo # MDEV-5709 ExtractValue() with XPath variable references returns wrong result
+--echo #
+CREATE TABLE t1 (c1 INT, c2 VARCHAR(10));
+INSERT INTO t1 VALUES (1,'b1'),(2,'b2');
+SELECT *,IF(@i:=c1,ExtractValue('<a><b>b1</b><b>b2</b></a>','//b[$@i]'),0) AS xpath FROM t1;
+SELECT * FROM t1 WHERE c2=IF(@i:=c1,ExtractValue('<a><b>b1</b><b>b2</b></a>','//b[$@i]'),0);
+DROP TABLE t1;
+
+
+--echo #
+--echo # End of 10.0 tests
+--echo #
diff --git a/sql/item_xmlfunc.cc b/sql/item_xmlfunc.cc
index 75f0ed9ef5a..30db7e635e2 100644
--- a/sql/item_xmlfunc.cc
+++ b/sql/item_xmlfunc.cc
@@ -2600,16 +2600,24 @@ my_xpath_parse(MY_XPATH *xpath, const char *str, const char *strend)
void Item_xml_str_func::fix_length_and_dec()
{
+ max_length= MAX_BLOB_WIDTH;
+ agg_arg_charsets_for_comparison(collation, args, arg_count);
+}
+
+
+bool Item_xml_str_func::fix_fields(THD *thd, Item **ref)
+{
String *xp, tmp;
MY_XPATH xpath;
int rc;
+ if (Item_str_func::fix_fields(thd, ref))
+ return true;
+
status_var_increment(current_thd->status_var.feature_xml);
nodeset_func= 0;
- if (agg_arg_charsets_for_comparison(collation, args, arg_count))
- return;
if (collation.collation->mbminlen > 1)
{
@@ -2617,23 +2625,23 @@ void Item_xml_str_func::fix_length_and_dec()
my_printf_error(ER_UNKNOWN_ERROR,
"Character set '%s' is not supported by XPATH",
MYF(0), collation.collation->csname);
- return;
+ return true;
}
if (!args[1]->const_item())
{
my_printf_error(ER_UNKNOWN_ERROR,
"Only constant XPATH queries are supported", MYF(0));
- return;
+ return true;
}
if (!(xp= args[1]->val_str(&tmp)))
- return;
+ return false; // Will return NULL
my_xpath_init(&xpath);
xpath.cs= collation.collation;
xpath.debug= 0;
- xpath.pxml= &pxml;
- pxml.set_charset(collation.collation);
+ xpath.pxml= xml.parsed();
+ xml.set_charset(collation.collation);
rc= my_xpath_parse(&xpath, xp->ptr(), xp->ptr() + xp->length());
@@ -2643,13 +2651,24 @@ void Item_xml_str_func::fix_length_and_dec()
set_if_smaller(clen, 32);
my_printf_error(ER_UNKNOWN_ERROR, "XPATH syntax error: '%.*s'",
MYF(0), clen, xpath.lasttok.beg);
- return;
+ return true;
}
- nodeset_func= xpath.item;
- if (nodeset_func)
- nodeset_func->fix_fields(current_thd, &nodeset_func);
- max_length= MAX_BLOB_WIDTH;
+ /*
+ Parsing XML is a heavy operation, so if the first argument is constant,
+ then parse XML only one time and cache the parsed representation
+ together with raw text representation.
+
+ Note, we cannot cache the entire function result even if
+ the first and the second arguments are constants, because
+ the XPath expression may have user and SP variable references,
+ so the function result can vary between executions.
+ */
+ if ((args[0]->const_item() && get_xml(&xml, true)) ||
+ !(nodeset_func= xpath.item))
+ return false; // Will return NULL
+
+ return nodeset_func->fix_fields(thd, &nodeset_func);
}
@@ -2780,25 +2799,24 @@ int xml_leave(MY_XML_PARSER *st,const char *attr, size_t len)
Parse raw XML
SYNOPSYS
-
RETURN
- Currently pointer to parsed XML on success
- 0 on parse error
+ false on success
+ true on error
*/
-String *Item_xml_str_func::parse_xml(String *raw_xml, String *parsed_xml_buf)
+bool Item_xml_str_func::XML::parse()
{
MY_XML_PARSER p;
MY_XML_USER_DATA user_data;
int rc;
- parsed_xml_buf->length(0);
+ m_parsed_buf.length(0);
/* Prepare XML parser */
my_xml_parser_create(&p);
p.flags= MY_XML_FLAG_RELATIVE_NAMES | MY_XML_FLAG_SKIP_TEXT_NORMALIZATION;
user_data.level= 0;
- user_data.pxml= parsed_xml_buf;
+ user_data.pxml= &m_parsed_buf;
user_data.parent= 0;
my_xml_set_enter_handler(&p, xml_enter);
my_xml_set_value_handler(&p, xml_value);
@@ -2807,10 +2825,10 @@ String *Item_xml_str_func::parse_xml(String *raw_xml, String *parsed_xml_buf)
/* Add root node */
p.current_node_type= MY_XML_NODE_TAG;
- xml_enter(&p, raw_xml->ptr(), 0);
+ xml_enter(&p, m_raw_ptr->ptr(), 0);
/* Execute XML parser */
- if ((rc= my_xml_parse(&p, raw_xml->ptr(), raw_xml->length())) != MY_XML_OK)
+ if ((rc= my_xml_parse(&p, m_raw_ptr->ptr(), m_raw_ptr->length())) != MY_XML_OK)
{
char buf[128];
my_snprintf(buf, sizeof(buf)-1, "parse error at line %d pos %lu: %s",
@@ -2820,10 +2838,41 @@ String *Item_xml_str_func::parse_xml(String *raw_xml, String *parsed_xml_buf)
push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
ER_WRONG_VALUE,
ER(ER_WRONG_VALUE), "XML", buf);
+ m_raw_ptr= (String *) 0;
}
my_xml_parser_free(&p);
- return rc == MY_XML_OK ? parsed_xml_buf : 0;
+ return rc != MY_XML_OK;
+}
+
+
+/*
+ Parse the raw XML from the given source,
+ optionally cache the raw XML,
+ remember the pointer to the raw XML.
+*/
+bool Item_xml_str_func::XML::parse(String *raw_xml, bool cache)
+{
+ m_raw_ptr= raw_xml;
+ if (cache)
+ {
+ m_cached= true;
+ if (m_raw_ptr != &m_raw_buf && m_raw_buf.copy(*m_raw_ptr))
+ {
+ m_raw_ptr= (String *) 0;
+ return true;
+ }
+ m_raw_ptr= &m_raw_buf;
+ }
+ return parse();
+}
+
+
+const MY_XML_NODE *Item_xml_str_func::XML::node(uint idx)
+{
+ const MY_XML_NODE *nodebeg= (MY_XML_NODE*) m_parsed_buf.ptr();
+ DBUG_ASSERT(idx < m_parsed_buf.length() / sizeof (MY_XML_NODE));
+ return nodebeg + idx;
}
@@ -2831,10 +2880,8 @@ String *Item_func_xml_extractvalue::val_str(String *str)
{
String *res;
null_value= 0;
- if (!nodeset_func ||
- !(res= args[0]->val_str(str)) ||
- !parse_xml(res, &pxml) ||
- !(res= nodeset_func->val_str(&tmp_value)))
+ if (!nodeset_func || get_xml(&xml) ||
+ !(res= nodeset_func->val_str(str)))
{
null_value= 1;
return 0;
@@ -2843,22 +2890,37 @@ String *Item_func_xml_extractvalue::val_str(String *str)
}
+bool Item_func_xml_update::collect_result(String *str,
+ const MY_XML_NODE *cut,
+ const String *replace)
+{
+ uint offs= cut->type == MY_XML_NODE_TAG ? 1 : 0;
+ const char *end= cut->tagend + offs;
+ str->length(0);
+ str->set_charset(collation.collation);
+ return
+ /* Put the XML part preceeding the replaced piece */
+ str->append(xml.raw()->ptr(), cut->beg - xml.raw()->ptr() - offs) ||
+ /* Put the replacement */
+ str->append(replace->ptr(), replace->length()) ||
+ /* Put the XML part following the replaced piece */
+ str->append(end, xml.raw()->ptr() + xml.raw()->length() - end);
+}
+
+
String *Item_func_xml_update::val_str(String *str)
{
- String *res, *nodeset, *rep;
+ String *nodeset, *rep;
null_value= 0;
- if (!nodeset_func ||
- !(res= args[0]->val_str(str)) ||
+ if (!nodeset_func || get_xml(&xml) ||
!(rep= args[2]->val_str(&tmp_value3)) ||
- !parse_xml(res, &pxml) ||
!(nodeset= nodeset_func->val_nodeset(&tmp_value2)))
{
null_value= 1;
return 0;
}
- MY_XML_NODE *nodebeg= (MY_XML_NODE*) pxml.ptr();
MY_XPATH_FLT *fltbeg= (MY_XPATH_FLT*) nodeset->ptr();
MY_XPATH_FLT *fltend= (MY_XPATH_FLT*) (nodeset->ptr() + nodeset->length());
@@ -2866,10 +2928,10 @@ String *Item_func_xml_update::val_str(String *str)
if (fltend - fltbeg != 1)
{
/* TODO: perhaps add a warning that more than one tag selected */
- return res;
+ return xml.raw();
}
- nodebeg+= fltbeg->num;
+ const MY_XML_NODE *nodebeg= xml.node(fltbeg->num);
if (!nodebeg->level)
{
@@ -2881,12 +2943,5 @@ String *Item_func_xml_update::val_str(String *str)
return rep;
}
- tmp_value.length(0);
- tmp_value.set_charset(collation.collation);
- uint offs= nodebeg->type == MY_XML_NODE_TAG ? 1 : 0;
- tmp_value.append(res->ptr(), nodebeg->beg - res->ptr() - offs);
- tmp_value.append(rep->ptr(), rep->length());
- const char *end= nodebeg->tagend + offs;
- tmp_value.append(end, res->ptr() + res->length() - end);
- return &tmp_value;
+ return collect_result(str, nodebeg, rep) ? (String *) NULL : str;
}
diff --git a/sql/item_xmlfunc.h b/sql/item_xmlfunc.h
index e818a6da408..637f505e12e 100644
--- a/sql/item_xmlfunc.h
+++ b/sql/item_xmlfunc.h
@@ -26,11 +26,55 @@
#endif
+typedef struct my_xml_node_st MY_XML_NODE;
+
+
class Item_xml_str_func: public Item_str_func
{
protected:
- String tmp_value, pxml;
+ /*
+ A helper class to store raw and parsed XML.
+ */
+ class XML
+ {
+ bool m_cached;
+ String *m_raw_ptr; // Pointer to text representation
+ String m_raw_buf; // Cached text representation
+ String m_parsed_buf; // Array of MY_XML_NODEs, pointing to raw_buffer
+ bool parse();
+ void reset()
+ {
+ m_cached= false;
+ m_raw_ptr= (String *) 0;
+ }
+ public:
+ XML() { reset(); }
+ void set_charset(CHARSET_INFO *cs) { m_parsed_buf.set_charset(cs); }
+ String *raw() { return m_raw_ptr; }
+ String *parsed() { return &m_parsed_buf; }
+ const MY_XML_NODE *node(uint idx);
+ bool cached() { return m_cached; }
+ bool parse(String *raw, bool cache);
+ bool parse(Item *item, bool cache)
+ {
+ String *res;
+ if (!(res= item->val_str(&m_raw_buf)))
+ {
+ m_raw_ptr= (String *) 0;
+ m_cached= cache;
+ return true;
+ }
+ return parse(res, cache);
+ }
+ };
Item *nodeset_func;
+ XML xml;
+ bool get_xml(XML *xml, bool cache= false)
+ {
+ if (!cache && xml->cached())
+ return xml->raw() == 0;
+ return xml->parse(args[0], cache);
+ }
public:
Item_xml_str_func(Item *a, Item *b):
Item_str_func(a,b)
@@ -42,8 +86,12 @@ public:
{
maybe_null= TRUE;
}
+ bool fix_fields(THD *thd, Item **ref);
void fix_length_and_dec();
- String *parse_xml(String *raw_xml, String *parsed_xml_buf);
+ bool const_item() const
+ {
+ return const_item_cache && (!nodeset_func || nodeset_func->const_item());
+ }
bool check_vcol_func_processor(uchar *int_arg)
{
return trace_unsupported_by_check_vcol_func_processor(func_name());
@@ -63,6 +111,9 @@ public:
class Item_func_xml_update: public Item_xml_str_func
{
String tmp_value2, tmp_value3;
+ bool collect_result(String *str,
+ const MY_XML_NODE *cut,
+ const String *replace);
public:
Item_func_xml_update(Item *a,Item *b,Item *c) :Item_xml_str_func(a,b,c) {}
const char *func_name() const { return "updatexml"; }