From a60106917249420216892c38b7dd14adde97f228 Mon Sep 17 00:00:00 2001 From: "Alexey (Holyfoot) Botchkov" Date: Wed, 22 Jul 2026 12:48:27 +0300 Subject: [PATCH] MDEV-40394 XML schema fails on self-referencing type. A schema type that refers to itself, directly or through other types or groups, was validated with a single shared compositor state per type. A nested use of the type reset the state of the outer one, so XMLISVALID() gave wrong results for recursive schemas. Now every recursion level of an XMLSchema_user_type gets its own copy of the type's content (get_copy()/copy_tags()). Copies are kept on a per-type stack while validating, and released copies are put in m_c_free and reused, so memory doesn't grow with each validated row. reset_types() unwinds any state left by a validation that aborted. Other changes: - XMLSchema_item::is_validate_done() is replaced with ::end_validation(st). It is no longer a pure check: it pops the compositor stack and must be paired with validate_prepare(). - validate_prepare() can now fail, and the failure is propagated. - A recursion depth limit (MAX_RECURSION_DEPTH, 1024) is added, counted per type. When it is hit, or on OOM during validation, XMLISVALID() returns errors/warnings. - XMLSchema_group_def now derives from XMLSchema_user_type, so groups get the same copy mechanism. As a side effect is accepted. - XMLSchema_group_reference now counts occurrences, so minOccurs and maxOccurs of a group reference are enforced. - XMLSchema_group_def::check_type() checks for circular groups, and XMLSchema_attribute::check_type() is removed (named types and groups are checked globally). - The circular-reference error names the construct: "complexType B has bad circular references" instead of "type B has ...". The affected .result lines are re-recorded. - Hook chains (m_next_tag) are replaced with List<> because tags are now copied. --- plugin/type_xmltype/item_func_xml_isvalid.cc | 825 ++++++++++++++---- .../type_xmltype_validation.result | 714 ++++++++++++++- .../type_xmltype/type_xmltype_validation.test | 609 ++++++++++++- 3 files changed, 1963 insertions(+), 185 deletions(-) diff --git a/plugin/type_xmltype/item_func_xml_isvalid.cc b/plugin/type_xmltype/item_func_xml_isvalid.cc index 48be37d8435..e4b7381d024 100644 --- a/plugin/type_xmltype/item_func_xml_isvalid.cc +++ b/plugin/type_xmltype/item_func_xml_isvalid.cc @@ -127,6 +127,7 @@ class XMLSchema_item: public Sql_alloc { public: XMLSchema_item(const XMLSchema_item &)= delete; + void operator=(const XMLSchema_item &) = delete; XMLSchema_item() {} @@ -169,7 +170,8 @@ public: return false; } - virtual void validate_prepare() {} + /* returns TRUE in the case of error (mostly OOM). */ + virtual bool validate_prepare(MY_XML_VALIDATION_DATA *st) { return FALSE; } enum vtn_result{ VTN_ACCEPTED, /* tag accepted by this rule. */ VTN_CONTINUE, /* tag wasn't accepted, need to check other rules. */ @@ -182,7 +184,15 @@ public: return VTN_CONTINUE; } - virtual bool is_validate_done() { return true; } + /* + Called when the content of the rule ends (e.g. the enclosing tag + is closed) to check that the rule is satisfied by what was parsed, + like the minOccurs constraints and the required nested rules. + Returns TRUE if the validation can go on, FALSE if the XML is invalid. + The function CAN change the state of the object, every + validate_prepare() needs one end_validation(). + */ + virtual bool end_validation(MY_XML_VALIDATION_DATA *st) { return true; } virtual bool validate_value(MY_XML_VALIDATION_DATA *st, @@ -202,13 +212,7 @@ public: return MY_XML_OK; } virtual bool validate_attr(MY_XML_VALIDATION_DATA *st, - const char *attr, size_t len) - { - return MY_XML_ERROR; - } - - int validate_failed(MY_XML_VALIDATION_DATA *st); - + const char *attr, size_t len); class XMLSchema_item *m_next; }; @@ -303,6 +307,11 @@ public: { return len == m_val_len && memcmp(m_val, name, len) == 0; } + void copy_attribute(XMLSchema_tag_attribute *c) const + { + c->m_val= m_val; + c->m_val_len= m_val_len; + } }; @@ -332,6 +341,10 @@ public: return m_error ? MY_XML_ERROR : MY_XML_OK; } + void copy_int_attribute(XMLSchema_tag_integer_attribute *c) const + { + c->m_value_int= m_value_int; + } }; @@ -388,7 +401,15 @@ public: class MY_XML_VALIDATION_DATA: public Sql_alloc { public: - int validation_failed; + enum v_result + { + VALIDATION_OK= 0, + XML_DOESNT_FIT_SCHEMA= 1, + OOM_ERROR= 2, + REC_LIMIT_ERROR= 4 + }; + + uint validation_result; uint attr; XMLSchema_item skipped_attr; XMLSchema_annotation annotation; @@ -415,7 +436,7 @@ public: void pop() { s_stack= s_stack->m_next; } - void add_item_to_resolve(XMLSchema_item *t); + bool add_item_to_resolve(XMLSchema_item *t); bool set_schema_namespace(const LEX_CSTRING *ns) { /* @@ -451,6 +472,22 @@ public: size_t len) const; XMLSchema_group_def *find_element_group_by_name(const char *name, size_t len) const; + int validation_failed() + { + validation_result|= XML_DOESNT_FIT_SCHEMA; + return MY_XML_ERROR; + } + + int error_oom() + { + validation_result|= OOM_ERROR; + return MY_XML_ERROR; + } + int error_recursion_limit() + { + validation_result|= REC_LIMIT_ERROR; + return MY_XML_ERROR; + } }; @@ -478,10 +515,10 @@ bool XMLSchema_item::leave(MY_XML_VALIDATION_DATA *st, } -int XMLSchema_item::validate_failed(MY_XML_VALIDATION_DATA *st) +bool XMLSchema_item::validate_attr(MY_XML_VALIDATION_DATA *st, + const char *attr, size_t len) { - st->validation_failed= 1; - return MY_XML_ERROR; + return st->validation_failed(); } @@ -494,7 +531,6 @@ class XMLSchema_tag: public XMLSchema_item public: XMLSchema_tag_attribute *m_tag_attributes; XMLSchema_tag_attribute m_id; /* eveny tag in schema has the "id" attr */ - XMLSchema_tag *m_next_tag; XMLSchema_annotation *m_annotation; bool declare_attribute(XMLSchema_tag_attribute *attr) @@ -518,6 +554,16 @@ public: { st->push(this); } + /* + If the tag has any runtime data, it should be able to + create its copy. + Otherwise we return the same instance to be used. + */ + virtual XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) + { + return this; + } + virtual bool is_emptyable() { return false; } }; @@ -584,8 +630,7 @@ public: bool resolve_type(MY_XML_VALIDATION_DATA *st, LEX_CSTRING *bad_type) override; - bool check_type() override; - void validate_prepare() override; + bool validate_prepare(MY_XML_VALIDATION_DATA *st) override; bool validate_value(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; bool validate_leave(MY_XML_VALIDATION_DATA *st, @@ -1272,7 +1317,7 @@ public: st->push(&st->annotation); return VTN_ACCEPTED; } - bool is_validate_done() override + bool end_validation(MY_XML_VALIDATION_DATA *st) override { return true; } @@ -1288,18 +1333,18 @@ public: bool validate_tag(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override { - return validate_failed(st); + return st->validation_failed(); } virtual bool validate_attr(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override { - return validate_failed(st); + return st->validation_failed(); } bool validate_value(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override { return m_int_type->valid_value(attr, len) ? MY_XML_OK : - validate_failed(st); + st->validation_failed(); } }; @@ -1307,17 +1352,47 @@ public: class XMLSchema_user_type: public XMLSchema_type { protected: + static const int MAX_RECURSION_DEPTH= 1024; + class Compositor_stack_node : public Sql_alloc + { + public: + XMLSchema_tag *m_compositor; + Compositor_stack_node *m_next; + }; + /* + Stack of the saved compositors of the outer (still unfinished) + validation levels. When the type is used recursively, validate_prepare() + pushes the current m_compositor here and continues with a fresh copy. + end_validation() pops it back. + */ + Compositor_stack_node *m_c_stack; + /* + List of the stack nodes (with their compositor copies) that were + already created and are not in use at the moment. They're reused + by validate_prepare() instead of allocating new copies on the MEM_ROOT. + */ + Compositor_stack_node *m_c_free; + XMLSchema_schema *m_schema; XMLSchema_tag_attribute m_type_name; XMLSchema_tag_attribute m_final; + /* + Current nesting level of the validation of this type: number of + validate_prepare() calls not yet matched by end_validation(). + Non-zero means the type is being validated (so a further use is + a recursion). Limited by MAX_RECURSION_DEPTH. + */ + int m_in_validate; public: XMLSchema_tag *m_compositor; XMLSchema_user_type *m_next_type; XMLSchema_user_type(XMLSchema_schema *schema): XMLSchema_type(), + m_c_stack(NULL), m_c_free(NULL), m_schema(schema), m_type_name(&xs_name), m_final(&xs_final), + m_in_validate(0), m_compositor(NULL) { declare_attribute(&m_type_name); @@ -1329,6 +1404,34 @@ public: { return m_compositor->check_type(); } + /* + Unwinds the validation state of the type: empties the m_c_stack + (restoring the original m_compositor) and zeroes m_in_validate. + + Must be called after every validation of the schema, whether it + succeeded or failed (XMLSchema_schema::reset_types() does that for + all the types of the schema). A validation that aborts before the + parsing ends leaves the type 'hot': with m_in_validate != 0 and + pushed compositor copies, so the next validation would take + the type for a recursion and use a wrong compositor state. + It's safe to call it on a type that is not 'hot'. + */ + void reset_type() + { + m_in_validate= 0; + while (m_c_stack) + { + Compositor_stack_node *tmp= m_c_stack; + XMLSchema_tag *tmp_compositor= m_c_stack->m_compositor; + + m_c_stack->m_compositor= m_compositor; + m_compositor= tmp_compositor; + + m_c_stack= m_c_stack->m_next; + tmp->m_next= m_c_free; + m_c_free= tmp; + } + } void get_type_name(LEX_CSTRING *name) const { name->str= m_type_name.m_val; @@ -1338,13 +1441,79 @@ public: { return m_type_name.eq_value(attr, len); } - void validate_prepare() override + bool validate_prepare(MY_XML_VALIDATION_DATA *st) override { - m_compositor->validate_prepare(); + if (m_in_validate) + { + /* + We get here when the type is used recursively. + Put the current state in the stack and clone the + new state. + */ + Compositor_stack_node *csn; + XMLSchema_tag *comp; + + /* + Every level of recursion allocates a compositor copy on the + statement MEM_ROOT, so the depth has to be limited. + Otherwise malignant user can produce OOM affecting the server + with a huge XML consisting of recurrent type. + */ + if (m_in_validate >= MAX_RECURSION_DEPTH) + { + st->error_recursion_limit(); + return TRUE; + } + + /* try to recycle already alloced compositor first. */ + if (m_c_free) + { + csn= m_c_free; + m_c_free= csn->m_next; + comp= csn->m_compositor; + } + else + { + csn= new(st->mem_root) Compositor_stack_node; + comp= m_compositor->get_copy(st); + + if (!csn || !comp) + { + st->error_oom(); + return TRUE; + } + } + + csn->m_next= m_c_stack; + m_c_stack= csn; + csn->m_compositor= m_compositor; + m_compositor= comp; + } + + m_in_validate++; + + return m_compositor->validate_prepare(st); } - bool is_validate_done() override + bool end_validation(MY_XML_VALIDATION_DATA *st) override { - return m_compositor->is_validate_done(); + bool res; + + res= m_compositor->end_validation(st); + + DBUG_ASSERT(m_in_validate); + + if (--m_in_validate) + { + /* pop the compositor from the stack. */ + Compositor_stack_node *c_node= m_c_stack; + m_c_stack= c_node->m_next; + XMLSchema_tag *cur= m_compositor; + m_compositor= c_node->m_compositor; + c_node->m_compositor= cur; + c_node->m_next= m_c_free; + m_c_free= c_node; + } + return res; } bool validate_value(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override @@ -1397,7 +1566,7 @@ public: if (m_attributes.validate_attr(st, attr, len) == MY_XML_OK || m_compositor->validate_attr(st, attr, len) == MY_XML_OK) return MY_XML_OK; - return MY_XML_ERROR; + return st->validation_failed(); } enum vtn_result validate_tag_name(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override @@ -1481,9 +1650,13 @@ public: m_in_check_type= false; return res; } - void validate_prepare() override + bool validate_prepare(MY_XML_VALIDATION_DATA *st) override { - m_base_type->validate_prepare(); + return m_base_type->validate_prepare(st); + } + bool end_validation(MY_XML_VALIDATION_DATA *st) override + { + return m_base_type->end_validation(st); } bool validate_value(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; @@ -1565,9 +1738,13 @@ public: return res; } - void validate_prepare() override + bool validate_prepare(MY_XML_VALIDATION_DATA *st) override { - m_base_type->validate_prepare(); + return m_base_type->validate_prepare(st); + } + bool end_validation(MY_XML_VALIDATION_DATA *st) override + { + return m_base_type->end_validation(st); } bool validate_attr(MY_XML_VALIDATION_DATA *st, @@ -1611,13 +1788,10 @@ public: class XMLSchema_union: public XMLSchema_tag { XMLSchema_tag_attribute m_attr_memberTypes; - XMLSchema_user_type *m_nested_types; /* we have to preserver the order here */ - XMLSchema_user_type **m_nested_hook; + List m_nested_types; public: XMLSchema_union(): XMLSchema_tag(), - m_attr_memberTypes(&xs_memberTypes), - m_nested_types(NULL), - m_nested_hook(&m_nested_types) + m_attr_memberTypes(&xs_memberTypes) { declare_attribute(&m_attr_memberTypes); } @@ -1639,7 +1813,14 @@ public: const char *attr, size_t len) override; bool leave(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; bool check_type() override { return m_nested->check_type(); } - void validate_prepare() override { m_nested->validate_prepare(); } + bool validate_prepare(MY_XML_VALIDATION_DATA *st) override + { + return m_nested->validate_prepare(st); + } + bool end_validation(MY_XML_VALIDATION_DATA *st) override + { + return m_nested->end_validation(st); + } bool validate_attr(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override { @@ -1678,52 +1859,56 @@ class XMLSchema_all: public XMLSchema_tag protected: XMLSchema_tag_integer_attribute m_minOccurs; XMLSchema_tag_unbounded_integer_attribute m_maxOccurs; - XMLSchema_tag **m_tags_hook; public: int m_counter; - XMLSchema_tag *m_tags; + List m_tags_list; XMLSchema_all(): XMLSchema_tag(), m_minOccurs(&xs_minOccurs), - m_maxOccurs(&xs_maxOccurs), - m_tags_hook(&m_tags), m_tags(NULL) + m_maxOccurs(&xs_maxOccurs) { declare_attribute(&m_minOccurs); declare_attribute(&m_maxOccurs); } - void append_tag(XMLSchema_tag *tag); + bool append_tag(XMLSchema_tag *tag, MEM_ROOT *r); bool enter_tag(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; - bool leave(MY_XML_VALIDATION_DATA *st, - const char *attr, size_t len) override - { - *m_tags_hook= NULL; - return XMLSchema_tag::leave(st, attr, len); - } bool check_type() override { - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + while ((cur= it++)) { if(cur->check_type()) return TRUE; } return FALSE; } - void validate_prepare() override + bool validate_prepare(MY_XML_VALIDATION_DATA *st) override { + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + m_counter= 0; - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) - cur->validate_prepare(); + + while ((cur= it++)) + { + if (cur->validate_prepare(st)) + return TRUE; + } + return FALSE; } enum vtn_result validate_tag_name(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override { + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; enum vtn_result tn_result; - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) + while ((cur= it++)) { if ((tn_result= cur->validate_tag_name(st, attr, len)) != VTN_CONTINUE) { @@ -1732,21 +1917,48 @@ public: return tn_result; } } - return is_validate_done() ? VTN_CONTINUE : VTN_ERROR; + return VTN_CONTINUE; } - bool is_validate_done() override + bool end_validation(MY_XML_VALIDATION_DATA *st) override { + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + bool res= true;; + + while ((cur= it++)) + { + if (!cur->end_validation(st)) + res= false; + } + if (m_counter == 0) { return m_minOccurs.m_value_int == 0; } - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) + return res; + } + bool copy_tags(MY_XML_VALIDATION_DATA *st, XMLSchema_all *c); + XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) override + { + XMLSchema_all *c= new(st->mem_root) XMLSchema_all(); + if (!c || copy_tags(st, c)) + return NULL; + + return c; + } + bool is_emptyable() override + { + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + + if (m_minOccurs.m_value_int == 0) + return true; + + while ((cur= it++)) { - if (!cur->is_validate_done()) - { + if (!cur->is_emptyable()) return false; - } } return true; } @@ -1756,13 +1968,16 @@ public: class XMLSchema_sequence: public XMLSchema_all { XMLSchema_tag *m_cur_tag; + List_iterator_fast m_cur_list; public: bool enter_tag(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; - void validate_prepare() override + bool validate_prepare(MY_XML_VALIDATION_DATA *st) override { m_cur_tag= NULL; + m_cur_list.init(m_tags_list); m_counter= 0; + return FALSE; } enum vtn_result validate_tag_name(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override @@ -1773,32 +1988,40 @@ public: if (!m_cur_tag) { - if (!m_tags) + if (m_tags_list.is_empty()) return VTN_CONTINUE; turn_over: if (m_counter >= m_maxOccurs.m_value_int) return VTN_CONTINUE; - m_cur_tag= m_tags; + m_cur_list.rewind(); + m_cur_tag= m_cur_list++; m_counter++; beginning= true; - m_cur_tag->validate_prepare(); + if (m_cur_tag->validate_prepare(st)) + return VTN_ERROR; } while ((tn_result= m_cur_tag->validate_tag_name(st, attr, len)) == VTN_CONTINUE) { - if (!m_cur_tag->is_validate_done()) + if (!m_cur_tag->end_validation(st)) { /* If we already applied some elements of the sequence, then we have to break the validation with the error. Otherwise we still can try other rules. */ - return beginning ? VTN_CONTINUE : VTN_ERROR; + m_cur_tag= NULL; + if (beginning) + { + m_counter--; + return VTN_CONTINUE; + } + return VTN_ERROR; } - m_cur_tag= m_cur_tag->m_next_tag; + m_cur_tag= m_cur_list++; if (!m_cur_tag) { if (full_loop) @@ -1812,27 +2035,44 @@ turn_over: full_loop= true; goto turn_over; } - m_cur_tag->validate_prepare(); + if (m_cur_tag->validate_prepare(st)) + return VTN_ERROR; } return tn_result; } - bool is_validate_done() override + bool end_validation(MY_XML_VALIDATION_DATA *st) override { - if (m_counter < m_minOccurs.m_value_int) - return false; - while (m_cur_tag) + if (m_cur_tag) { - if (!m_cur_tag->is_validate_done()) + /* + some items were accepted then the XML piece ended. + need to check the rest of the list. + */ + if (!m_cur_tag->end_validation(st)) return false; - m_cur_tag= m_cur_tag->m_next_tag; - if (m_cur_tag) - m_cur_tag->validate_prepare(); + while ((m_cur_tag= m_cur_list++)) + { + if (!m_cur_tag->is_emptyable()) + return false; + } } + if (m_counter < m_minOccurs.m_value_int) + return is_emptyable(); + + return true; } + XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) override + { + XMLSchema_sequence *c= new(st->mem_root) XMLSchema_sequence(); + if (!c || copy_tags(st, c)) + return NULL; + + return c; + } }; @@ -1840,14 +2080,11 @@ class XMLSchema_choice: public XMLSchema_sequence { XMLSchema_tag *m_found; public: - void validate_prepare() override + bool validate_prepare(MY_XML_VALIDATION_DATA *st) override { - m_counter= 0; m_found= NULL; - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) - { - cur->validate_prepare(); - } + m_counter= 0; + return false; } enum vtn_result validate_tag_name(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override @@ -1859,7 +2096,8 @@ public: if ((tn_result= m_found->validate_tag_name(st, attr, len)) == VTN_CONTINUE) { - if (!m_found->is_validate_done()) + if (!m_found->end_validation(st) || + m_found->validate_prepare(st)) return VTN_ERROR; m_found= NULL; } @@ -1870,52 +2108,112 @@ public: if (m_counter >= m_maxOccurs.m_value_int) return VTN_CONTINUE; - - for (XMLSchema_tag *cur= m_tags; cur; cur= cur->m_next_tag) { - if ((tn_result= cur->validate_tag_name(st, attr, len)) == VTN_ACCEPTED) - { - m_found= cur; - m_counter++; - return VTN_ACCEPTED; - } + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; - if (tn_result == VTN_ERROR) - return VTN_ERROR; + while ((cur= it++)) + { + if (cur->validate_prepare(st)) + return VTN_ERROR; + + if ((tn_result= cur->validate_tag_name(st, attr, len)) == VTN_ACCEPTED) + { + m_found= cur; + m_counter++; + return VTN_ACCEPTED; + } + + if (tn_result == VTN_ERROR) + return VTN_ERROR; + + (void) cur->end_validation(st); + } } return VTN_CONTINUE; } - bool is_validate_done() override + bool end_validation(MY_XML_VALIDATION_DATA *st) override { + bool res= true; + if (m_found) { - if (!m_found->is_validate_done()) - return false; + if (!m_found->end_validation(st)) + res= false; } - return m_counter >= m_minOccurs.m_value_int; + + return res && + (m_counter >= m_minOccurs.m_value_int || is_emptyable()); + } + XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) override + { + XMLSchema_choice *c= new(st->mem_root) XMLSchema_choice(); + if (!c || copy_tags(st, c)) + return NULL; + + return c; + } + bool is_emptyable() override + { + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + + if (m_minOccurs.m_value_int == 0 || + m_tags_list.is_empty()) + return true; + + while ((cur= it++)) + { + if (cur->is_emptyable()) + return true; + } + + return false; } }; -class XMLSchema_group_def: public XMLSchema_tag +class XMLSchema_group_def: public XMLSchema_user_type { public: - XMLSchema_tag *m_compositor; - XMLSchema_tag_attribute m_atr_name; + bool m_in_check_type; XMLSchema_group_def *m_next_group; - XMLSchema_type *m_type; - XMLSchema_group_def(): XMLSchema_tag(), - m_compositor(NULL), - m_atr_name(&xs_name) - { - declare_attribute(&m_atr_name); - } + XMLSchema_group_def(): XMLSchema_user_type(NULL), + m_in_check_type(false) {} bool enter_tag(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; bool leave(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; + bool check_type() override + { + bool res; + + if (m_in_check_type) + return TRUE; /* circular groups are not allowed. */ + + m_in_check_type= true; + res= m_compositor->check_type(); + m_in_check_type= false; + return res; + } + void get_group_name(LEX_CSTRING *name) const + { + name->str= m_type_name.m_val; + name->length= m_type_name.m_val_len; + } + enum vtn_result validate_tag_name(MY_XML_VALIDATION_DATA *st, + const char *attr, size_t len) override + { + return m_compositor->validate_tag_name(st, attr, len); + } + + const XMLSchema_tag_attribute &name() const { return m_type_name; } + bool is_emptyable() override + { + return m_compositor->is_emptyable(); + } }; @@ -1925,12 +2223,15 @@ class XMLSchema_group_reference: public XMLSchema_tag XMLSchema_tag_unbounded_integer_attribute m_maxOccurs; XMLSchema_tag_attribute m_ref; XMLSchema_group_def *m_group; + int m_counter; + bool m_group_prepared; public: XMLSchema_group_reference(): XMLSchema_tag(), m_minOccurs(&xs_minOccurs), m_maxOccurs(&xs_maxOccurs), m_ref(&xs_ref), - m_group(NULL) + m_group(NULL), + m_group_prepared(false) { declare_attribute(&m_minOccurs); declare_attribute(&m_maxOccurs); @@ -1940,28 +2241,86 @@ public: bool leave(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override { - st->add_item_to_resolve(this); + if (st->add_item_to_resolve(this)) + return st->error_oom(); return XMLSchema_tag::leave(st, attr, len); } bool resolve_type(MY_XML_VALIDATION_DATA *st, LEX_CSTRING *bad_type) override; - - bool check_type() override + bool validate_prepare(MY_XML_VALIDATION_DATA *st) override { - return m_group->m_compositor->check_type(); - } - void validate_prepare() override - { - m_group->m_compositor->validate_prepare(); + m_counter= 0; + m_group_prepared= false; + return FALSE; } enum vtn_result validate_tag_name(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override { - return m_group->m_compositor->validate_tag_name(st, attr, len); + enum vtn_result res; + +retry_group: + if (m_counter >= m_maxOccurs.m_value_int) + return VTN_CONTINUE; + + if (!m_group_prepared) + { + if (m_group->validate_prepare(st)) + return VTN_ERROR; + } + res= m_group->validate_tag_name(st, attr, len); + if (res == VTN_CONTINUE) + { + bool ev= m_group->end_validation(st); + if (!m_group_prepared) + { + /* + No elements of the group matched. + If all the group elements are optional, the + 'no elements found' case is still valid. + */ + return VTN_CONTINUE; + } + m_group_prepared= false; + if (!ev) + { + /* Group wasn't finished. */; + return VTN_ERROR; + } + + m_counter++; + goto retry_group; + } + + m_group_prepared= true; + return res; } - bool is_validate_done() override + bool end_validation(MY_XML_VALIDATION_DATA *st) override { - return m_group->m_compositor->is_validate_done(); + if (m_group_prepared) + { + if (!m_group->end_validation(st)) + return false; /* group wasn't completed. */ + m_group_prepared= false; + m_counter++; + } + return m_counter >= m_minOccurs.m_value_int || m_group->is_emptyable(); + } + XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) override + { + XMLSchema_group_reference *c= + new(st->mem_root) XMLSchema_group_reference(); + if (!c) + return NULL; + + c->m_group= m_group; + m_minOccurs.copy_int_attribute(&c->m_minOccurs); + m_maxOccurs.copy_int_attribute(&c->m_maxOccurs); + return c; + } + bool check_type() override{ return m_group->check_type(); } + bool is_emptyable() override + { + return m_group->is_emptyable() || m_minOccurs.m_value_int == 0; } }; @@ -2001,7 +2360,6 @@ public: const char *attr, size_t len) override; bool validate_leave(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override; - }; @@ -2025,9 +2383,10 @@ public: declare_attribute(&m_atr_maxOccurs); declare_attribute(&m_atr_form); } - void validate_prepare() override + bool validate_prepare(MY_XML_VALIDATION_DATA *st) override { m_counter= 0; + return FALSE; } enum vtn_result validate_tag_name(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) override @@ -2038,17 +2397,35 @@ public: if (m_atr_name.eq_value(attr, len)) { m_counter++; - m_type->validate_prepare(); + if (m_type->validate_prepare(st)) + return VTN_ERROR; + st->push(this); return VTN_ACCEPTED; } return VTN_CONTINUE; } - bool is_validate_done() override + bool end_validation(MY_XML_VALIDATION_DATA *st) override { return m_counter >= m_atr_minOccurs.m_value_int; } + XMLSchema_tag *get_copy(MY_XML_VALIDATION_DATA *st) override + { + XMLSchema_element_local *c= new(st->mem_root) XMLSchema_element_local(); + if (!c) + return NULL; + + c->m_type= m_type; + m_atr_minOccurs.copy_int_attribute(&c->m_atr_minOccurs); + m_atr_maxOccurs.copy_int_attribute(&c->m_atr_maxOccurs); + m_atr_name.copy_attribute(&c->m_atr_name); + return c; + } + bool is_emptyable() override + { + return m_atr_minOccurs.m_value_int == 0; + } }; @@ -2113,6 +2490,8 @@ class XMLSchema_schema: public XMLSchema_tag public: XMLSchema_user_type *m_global_simpleTypes; XMLSchema_user_type *m_global_complexTypes; + XMLSchema_user_type *m_anonymous_types; + XMLSchema_attribute *m_global_attributes; XMLSchema_element_global *m_global_elements; XMLSchema_attributeGroup_def *m_global_attr_groups; @@ -2128,6 +2507,7 @@ public: m_atr_xml_lang(&xs_xml_lang), m_global_simpleTypes(NULL), m_global_complexTypes(NULL), + m_anonymous_types(NULL), m_global_attributes(NULL), m_global_elements(NULL), m_global_attr_groups(NULL), @@ -2164,7 +2544,8 @@ public: XMLSchema_type *find_simple_type(const char *name, size_t len) const; XMLSchema_type *find_complex_type(const char *name, size_t len) const; - bool check_types(LEX_CSTRING *bad_type); + bool check_types(LEX_CSTRING *tag_name, LEX_CSTRING *bad_type); + void reset_types(); }; @@ -2328,9 +2709,9 @@ XMLSchema_builtin_type *XMLSchema_builtin_type::get_builtin_type_by_name( } -void MY_XML_VALIDATION_DATA::add_item_to_resolve(XMLSchema_item *t) +bool MY_XML_VALIDATION_DATA::add_item_to_resolve(XMLSchema_item *t) { - schema->m_items_to_resolve.push_back(t, mem_root); + return schema->m_items_to_resolve.push_back(t, mem_root); } @@ -2469,7 +2850,8 @@ int XMLSchema_std_attributes::validate_attr(MY_XML_VALIDATION_DATA *st, m_anyAttribute->push_self(st); return MY_XML_OK; } - return MY_XML_ERROR; + + return st->validation_failed(); } @@ -2506,7 +2888,8 @@ bool XMLSchema_attribute::leave(MY_XML_VALIDATION_DATA *st, } else { - st->add_item_to_resolve(this); + if (st->add_item_to_resolve(this)) + return st->error_oom(); } return XMLSchema_item::leave(st, attr, len); @@ -2533,7 +2916,8 @@ bool XMLSchema_attributeGroup_reference::leave(MY_XML_VALIDATION_DATA *st, if (!m_atr_ref.is_set()) return MY_XML_ERROR; - st->add_item_to_resolve(this); + if (st->add_item_to_resolve(this)) + return st->error_oom(); return XMLSchema_tag::leave(st, attr, len); } @@ -2596,6 +2980,11 @@ bool XMLSchema_simpleType::leave(MY_XML_VALIDATION_DATA *st, m_next_type= m_schema->m_global_simpleTypes; m_schema->m_global_simpleTypes= this; } + else + { + m_next_type= st->schema->m_anonymous_types; + st->schema->m_anonymous_types= this; + } return XMLSchema_user_type::leave(st, attr, len); } @@ -2665,6 +3054,11 @@ bool XMLSchema_complexType::leave(MY_XML_VALIDATION_DATA *st, m_next_type= m_schema->m_global_complexTypes; m_schema->m_global_complexTypes= this; } + else + { + m_next_type= st->schema->m_anonymous_types; + st->schema->m_anonymous_types= this; + } return XMLSchema_user_type::leave(st, attr, len); } @@ -2740,7 +3134,8 @@ bool XMLSchema_restriction_in_simpleType::leave( if (m_base_type) return MY_XML_ERROR; /* type should be specified only once. */ - st->add_item_to_resolve(this); + if (st->add_item_to_resolve(this)) + return st->error_oom(); } else { @@ -2776,7 +3171,7 @@ bool XMLSchema_restriction_in_simpleType::validate_value( if (en->eq(attr, len)) return MY_XML_OK; } - return MY_XML_ERROR; + return st->validation_failed(); } /* TODO check other facets. */ @@ -2792,7 +3187,8 @@ bool XMLSchema_extension_in_simpleContent::leave( if (m_base_type) return MY_XML_ERROR; /* type should be specified only once. */ - st->add_item_to_resolve(this); + if (st->add_item_to_resolve(this)) + return st->error_oom(); } else { @@ -2856,7 +3252,9 @@ bool XMLSchema_list::leave(MY_XML_VALIDATION_DATA *st, if (!m_attr_itemType.is_set()) return MY_XML_ERROR; /* type must be specified. */ - st->add_item_to_resolve(this); + if (st->add_item_to_resolve(this)) + return st->error_oom(); + return res; } @@ -2882,10 +3280,12 @@ bool XMLSchema_union::enter_tag(MY_XML_VALIDATION_DATA *st, if (xs_simpleType.eq(attr, len)) { XMLSchema_simpleType *t= new(st->mem_root) XMLSchema_simpleType; + if (!t) + return st->error_oom(); + st->push(t); - *m_nested_hook= t; - m_nested_hook= &t->m_next_type; - return MY_XML_OK; + return m_nested_types.push_back(t, st->mem_root) ? + st->error_oom() : MY_XML_OK; } return XMLSchema_tag::enter_tag(st, attr, len); @@ -2897,9 +3297,7 @@ bool XMLSchema_union::leave(MY_XML_VALIDATION_DATA *st, { bool res= XMLSchema_tag::leave(st, attr, len); - *m_nested_hook= NULL; - - if (m_nested_types) + if (m_nested_types.size()) return m_attr_memberTypes.is_set() ? MY_XML_ERROR : res; return m_attr_memberTypes.is_set() ? res : MY_XML_ERROR; @@ -2976,10 +3374,9 @@ bool XMLSchema_complexContent::enter_tag(MY_XML_VALIDATION_DATA *st, */ -void XMLSchema_all::append_tag(XMLSchema_tag *tag) +bool XMLSchema_all::append_tag(XMLSchema_tag *tag, MEM_ROOT *r) { - *m_tags_hook= tag; - m_tags_hook= &tag->m_next_tag; + return m_tags_list.push_back(tag, r); } @@ -2996,15 +3393,36 @@ bool XMLSchema_all::enter_tag(MY_XML_VALIDATION_DATA *st, return XMLSchema_tag::enter_tag(st, attr, len); if (def == NULL) - return MY_XML_ERROR; /* OOM */ + return st->error_oom(); + + if (append_tag(def, st->mem_root)) + return st->error_oom(); - append_tag(def); st->push(def); return MY_XML_OK; } + +bool XMLSchema_all::copy_tags(MY_XML_VALIDATION_DATA *st, XMLSchema_all *c) +{ + List_iterator_fast it(m_tags_list); + XMLSchema_tag *cur; + + while ((cur= it++)) + { + XMLSchema_tag *t; + if (!(t= cur->get_copy(st)) || + c->append_tag(t, st->mem_root)) + return st->error_oom(); + } + m_minOccurs.copy_int_attribute(&c->m_minOccurs); + m_maxOccurs.copy_int_attribute(&c->m_maxOccurs); + return FALSE; +} + + bool XMLSchema_sequence::enter_tag(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) { @@ -3030,9 +3448,11 @@ bool XMLSchema_sequence::enter_tag(MY_XML_VALIDATION_DATA *st, return XMLSchema_all::enter_tag(st, attr, len); if (def == NULL) - return MY_XML_ERROR; /* OOM */ + return st->error_oom(); + + if (append_tag(def, st->mem_root)) + return st->error_oom(); - append_tag(def); st->push(def); return MY_XML_OK; @@ -3075,7 +3495,7 @@ bool XMLSchema_group_def::leave(MY_XML_VALIDATION_DATA *st, if (!m_compositor) return MY_XML_ERROR; /* group with no compositor is not allowed. */ - return XMLSchema_tag::leave(st, attr, len); + return XMLSchema_user_type::leave(st, attr, len); } @@ -3165,7 +3585,7 @@ bool XMLSchema_element_global::validate_tag(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) { if (m_type->validate_tag_name(st, attr, len) != VTN_ACCEPTED) - return validate_failed(st); + return st->validation_failed(); return MY_XML_OK; } @@ -3175,7 +3595,7 @@ bool XMLSchema_element_global::validate_leave(MY_XML_VALIDATION_DATA *st, const char *attr, size_t len) { st->pop(); - return m_type->is_validate_done() ? MY_XML_OK : MY_XML_ERROR; + return m_type->end_validation(st) ? MY_XML_OK : st->validation_failed(); } @@ -3229,7 +3649,7 @@ bool XMLSchema_schema::enter_tag(MY_XML_VALIDATION_DATA *st, return XMLSchema_tag::enter_tag(st, attr, len); if (def == NULL) - return MY_XML_ERROR; /*OOM*/ + return st->error_oom(); st->push(def); return MY_XML_OK; @@ -3290,15 +3710,32 @@ XMLSchema_type *XMLSchema_schema::find_complex_type( return NULL; } -bool XMLSchema_schema::check_types(LEX_CSTRING *bad_type) + +bool XMLSchema_schema::check_types(LEX_CSTRING *tag_name, + LEX_CSTRING *bad_tag) { + { + XMLSchema_group_def *grp; + + for(grp= m_global_element_groups; grp; grp= grp->m_next_group) + { + if (grp->check_type()) + { + *tag_name= xs_group.m_w; + grp->get_group_name(bad_tag); + return TRUE; + } + } + } + XMLSchema_user_type *t; for(t= m_global_complexTypes; t; t= t->m_next_type) { if (t->check_type()) { - t->get_type_name(bad_type); + t->get_type_name(bad_tag); + *tag_name= xs_complexType.m_w; return TRUE; } } @@ -3307,7 +3744,8 @@ bool XMLSchema_schema::check_types(LEX_CSTRING *bad_type) { if (t->check_type()) { - t->get_type_name(bad_type); + t->get_type_name(bad_tag); + *tag_name= xs_simpleType.m_w; return TRUE; } } @@ -3315,6 +3753,32 @@ bool XMLSchema_schema::check_types(LEX_CSTRING *bad_type) return FALSE; } + +/* + Resets the validation state of all the types and groups of the schema. + Must be called after each validation, including a failed one - + see XMLSchema_user_type::reset_type(). +*/ +void XMLSchema_schema::reset_types() +{ + for(XMLSchema_group_def *grp= m_global_element_groups; + grp; grp= grp->m_next_group) + grp->reset_type(); + + XMLSchema_user_type *t; + + + for(t= m_global_complexTypes; t; t= t->m_next_type) + t->reset_type(); + + for(t= m_global_simpleTypes; t; t= t->m_next_type) + t->reset_type(); + + for(t= m_anonymous_types; t; t= t->m_next_type) + t->reset_type(); +} + + XMLSchema_type *XMLSchema_schema::find_simple_type_by_name( MY_XML_VALIDATION_DATA *st, const char *name, size_t len) const { @@ -3379,7 +3843,7 @@ XMLSchema_group_def *MY_XML_VALIDATION_DATA::find_element_group_by_name( for (XMLSchema_group_def *g=schema->m_global_element_groups; g; g=g->m_next_group) { - if (g->m_atr_name.eq_value(name, len)) + if (g->name().eq_value(name, len)) return g; } return NULL; @@ -3391,23 +3855,19 @@ bool XMLSchema_schema::validate_element(MY_XML_VALIDATION_DATA *st, { XMLSchema_element_global *e= find_element(attr, len); if (!e) - return validate_failed(st); + return st->validation_failed(); + + if (e->validate_prepare(st)) + return MY_XML_ERROR; - e->validate_prepare(); st->push(e); return MY_XML_OK; } -bool XMLSchema_attribute::check_type() +bool XMLSchema_attribute::validate_prepare(MY_XML_VALIDATION_DATA *st) { - return m_type->check_type(); -} - - -void XMLSchema_attribute::validate_prepare() -{ - m_type->validate_prepare(); + return m_type->validate_prepare(st); } @@ -3544,26 +4004,27 @@ static int schema_parse(THD *thd, const String *xml, { List_iterator_fast li( user_data->schema->m_items_to_resolve); - LEX_CSTRING bad_type; + LEX_CSTRING bad_name, bad_tag; XMLSchema_item *i; while ((i= li++)) { - if (i->resolve_type(user_data, &bad_type)) + if (i->resolve_type(user_data, &bad_name)) { my_printf_error(ER_UNKNOWN_ERROR, "Invalid XML schema, type %.*s not defined.", MYF(0), - (int) bad_type.length, bad_type.str); + (int) bad_name.length, bad_name.str); rc= 1; goto exit; } } - if (user_data->schema->check_types(&bad_type)) + if (user_data->schema->check_types(&bad_tag, &bad_name)) { my_printf_error(ER_UNKNOWN_ERROR, - "Invalid XML schema, type %.*s has bad circular references.", MYF(0), - (int) bad_type.length, bad_type.str); + "Invalid XML schema, %.*s %.*s has bad circular references.", MYF(0), + (int) bad_tag.length, bad_tag.str, + (int) bad_name.length, bad_name.str); rc= 1; goto exit; } @@ -3667,7 +4128,7 @@ static int validate_schema(const String *xml, my_xml_set_leave_handler(&p, validation_leave); my_xml_set_user_data(&p, (void*) user_data); - user_data->validation_failed= 0; + user_data->validation_result= MY_XML_VALIDATION_DATA::VALIDATION_OK; user_data->schema->m_next= NULL; user_data->s_stack= &user_data->root; @@ -3675,6 +4136,8 @@ static int validate_schema(const String *xml, rc= my_xml_parse(&p, xml->ptr(), xml->length()) == MY_XML_OK; my_xml_parser_free(&p); + user_data->schema->reset_types(); + return rc; } @@ -3682,9 +4145,31 @@ static int validate_schema(const String *xml, bool Item_func_xml_isvalid::val_bool() { String *xml= args[0]->val_str(&m_tmp_xml); + bool res; if ((null_value= !xml || m_data == NULL)) return FALSE; - return validate_schema(xml, m_data); + res= validate_schema(xml, m_data); + + if (!(m_data->validation_result & + (MY_XML_VALIDATION_DATA::REC_LIMIT_ERROR | + MY_XML_VALIDATION_DATA::OOM_ERROR))) + return res; + + /* Something went wrong. */ + null_value= 1; + + if (m_data->validation_result & + MY_XML_VALIDATION_DATA::REC_LIMIT_ERROR) + { + my_printf_error(ER_UNKNOWN_ERROR, + "The recursive depth limit for XML validation was overrun.", MYF(0)); + } + /* + (m_data->validation_result & MY_XML_VALIDATION_DATA::OOM_ERROR) + already supposed to be reported by the memory allocation. + */ + + return FALSE; } diff --git a/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.result b/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.result index c219da98a9a..69f597e7413 100644 --- a/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.result +++ b/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.result @@ -442,7 +442,7 @@ XMLISVALID(@xml, @schema) set @xml= ' '; select XMLISVALID(@xml, @schema); XMLISVALID(@xml, @schema) -0 +1 set @schema= ' @@ -513,7 +513,7 @@ XMLISVALID(@xml, @schema) set @xml= ' '; select XMLISVALID(@xml, @schema); XMLISVALID(@xml, @schema) -0 +1 set @schema= ' @@ -630,7 +630,7 @@ set @xml= ' '; select XMLISVALID(@xml, @schema); XMLISVALID(@xml, @schema) -0 +1 @@ -1027,8 +1027,8 @@ set @xml= ' 2026-06-01 '; -select XMLISVALID(@xml, @schema); -XMLISVALID(@xml, @schema) +select XMLISVALID(@xml, @schema) Ex0; +Ex0 0 set @schema= ' @@ -1075,8 +1075,8 @@ set @xml= ' 2026-06-01 '; -select XMLISVALID(@xml, @schema); -XMLISVALID(@xml, @schema) +select XMLISVALID(@xml, @schema) Ex1; +Ex1 1 set @schema= ' @@ -1121,8 +1121,8 @@ set @xml= ' 2026-06-01 '; -select XMLISVALID(@xml, @schema); -XMLISVALID(@xml, @schema) +select XMLISVALID(@xml, @schema) Ex1; +Ex1 1 set @schema= ' @@ -2095,7 +2095,7 @@ SELECT XMLISVALID('x', ') AS r; -ERROR HY000: Invalid XML schema, type B has bad circular references. +ERROR HY000: Invalid XML schema, complexType B has bad circular references. SELECT XMLISVALID('x', ' @@ -2110,7 +2110,7 @@ SELECT XMLISVALID('x', ') AS r; -ERROR HY000: Invalid XML schema, type B has bad circular references. +ERROR HY000: Invalid XML schema, complexType B has bad circular references. SELECT XMLISVALID( 'x', ' @@ -2118,4 +2118,694 @@ SELECT XMLISVALID( 'x', ' ) AS r; -ERROR HY000: Invalid XML schema, type A has bad circular references. +ERROR HY000: Invalid XML schema, simpleType A has bad circular references. + +MDEV-40394 XML schema fails on self-referencing type. + +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as V; +V +1 +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as Expect_1; +Expect_1 +1 +SELECT XMLISVALID('', +' + + + + +') as V; +V +0 +SELECT XMLISVALID(' ', +' + + + + +') as V; +V +0 +SELECT XMLISVALID(' ', +' + + + + + + +') as V; +V +1 +SELECT XMLISVALID(' a ', +' + + + + + + + + + + + + + + ') AS v; +ERROR HY000: Invalid XML schema, group G has bad circular references. +SELECT XMLISVALID(' a ', +' + + + + + + + + + + + + + + + ') AS v; +ERROR HY000: Invalid XML schema, group G has bad circular references. +SELECT XMLISVALID('io', +' + + + + + ') as A_expect_1; +A_expect_1 +1 +SELECT XMLISVALID('i1i2o1o2', +' + + + + + ') as B_expect_1; +B_expect_1 +1 +SELECT XMLISVALID('ab', +' + + + + + ') as C_expect_0; +C_expect_0 +0 +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as Expect_0; +Expect_0 +0 +# +# Recursion through a shared xs:group +# Every group_reference shares the group's single compositor, so a +# nested use of the group resets the state of the outer one. +# +SET @schema_group= ' + + + + + + + + + +'; +SET @schema_seq= ' + + + + + + + + + + + + + +'; +# Valid: nested holds its own , outer follows. Expect 1 +# (same structure without the group, for comparison, is also 1) +SELECT XMLISVALID('xy', @schema_group) AS group_valid, +XMLISVALID('xy', @schema_seq) AS seq_valid; +group_valid seq_valid +1 1 +# Valid, two levels deep. Expect 1 / 1 +SELECT XMLISVALID('xyz', @schema_group) AS group_valid, +XMLISVALID('xyz', @schema_seq) AS seq_valid; +group_valid seq_valid +1 1 +# Invalid: outer missing. Expect 0 / 0 +SELECT XMLISVALID('x', @schema_group) AS group_invalid, +XMLISVALID('x', @schema_seq) AS seq_invalid; +group_invalid seq_invalid +0 0 +# Invalid: inner missing. Expect 0 / 0 +SELECT XMLISVALID('y', @schema_group) AS group_invalid, +XMLISVALID('y', @schema_seq) AS seq_invalid; +group_invalid seq_invalid +0 0 +# +# xs:group reference occurrence counting. +# minOccurs/maxOccurs on a group reference, including an emptiable group. +# +SET @pre= ''; +SET @post= ' + + +'; +# Group with required content, minOccurs=2 maxOccurs=2 +SET @schema_group= CONCAT(@pre, ' + + + + + + ', @post); +# One occurrence: too few. Expect 0 +SELECT XMLISVALID('11x', @schema_group) AS grp; +grp +0 +# Two occurrences. Expect 1 +SELECT XMLISVALID('1122x', @schema_group) AS grp; +grp +1 +# Three occurrences: too many. Expect 0 +SELECT XMLISVALID('112233x', @schema_group) AS grp; +grp +0 +# Second occurrence incomplete. Expect 0 +SELECT XMLISVALID('112x', @schema_group) AS grp; +grp +0 +# Group with minOccurs=0 +SET @schema_group= CONCAT(@pre, ' + + + + + + ', @post); +# Group absent. Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; +grp +1 +# Group present. Expect 1 +SELECT XMLISVALID('11x', @schema_group) AS grp; +grp +1 +# Group present twice (maxOccurs defaults to 1). Expect 0 +SELECT XMLISVALID('1122x', @schema_group) AS grp; +grp +0 +# Group present but incomplete. Expect 0 +SELECT XMLISVALID('1x', @schema_group) AS grp; +grp +0 +# Group with minOccurs=1 maxOccurs=unbounded +SET @schema_group= CONCAT(@pre, ' + + + + + + ', @post); +# Absent: too few. Expect 0 +SELECT XMLISVALID('x', @schema_group) AS grp; +grp +0 +# Four occurrences. Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; +grp +1 +# Emptiable group (all particles optional), default minOccurs=1 +SET @schema_group= CONCAT(@pre, ' + + + + + ', @post); +# Group content absent. Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; +grp +1 +# Group content present. Expect 1 +SELECT XMLISVALID('1x', @schema_group) AS grp; +grp +1 +# Emptiable group, minOccurs=2 maxOccurs=3 +SET @schema_group= CONCAT(@pre, ' + + + + + ', @post); +# Group content absent. Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; +grp +1 +# Three . Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; +grp +1 +# Four : too many. Expect 0 +SELECT XMLISVALID('x', @schema_group) AS grp; +grp +0 +# Emptiable group reference as the last particle of a sequence +SET @schema_group= CONCAT(@pre, ' + + + + + + + + +'); +# Group content absent, no tag reaches the group reference. Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; +grp +1 +# Group content present. Expect 1 +SELECT XMLISVALID('x1', @schema_group) AS grp; +grp +1 +# Required missing. Expect 0 +SELECT XMLISVALID('1', @schema_group) AS grp; +grp +0 +# +# recursion depth limit and recovery after aborted validation +# +SET @schema_rec= ' + + + + + + + +'; +# Just below the depth limit. Expect 1 +SELECT XMLISVALID(CONCAT('', REPEAT('', 1021), 'x', +REPEAT('', 1021), ''), @schema_rec) AS below_limit; +below_limit +1 +SELECT XMLISVALID(CONCAT('', REPEAT('', 1100), 'x', +REPEAT('', 1100), ''), @schema_rec) AS over_limit; +ERROR HY000: The recursive depth limit for XML validation was overrun. +# Rows after an aborted validation are not affected by it +CREATE TABLE t1 (id INT, x LONGTEXT); +INSERT INTO t1 VALUES +(1, ''), +(2, 'x'), +(3, 'x'), +(4, 'x'), +(5, ''), +(6, 'x'); +SELECT id, XMLISVALID(x, @schema_rec) AS r FROM t1 ORDER BY id; +id r +1 0 +2 1 +3 0 +4 1 +5 0 +6 1 +DROP TABLE t1; +# Recursion through xs:choice +SET @schema_rec= ' + + + + + + + +'; +# Expect 1 +SELECT XMLISVALID('a', @schema_rec) AS r; +r +1 +# Expect 0 +SELECT XMLISVALID('ab', @schema_rec) AS r; +r +0 +# Expect 0 +SELECT XMLISVALID('', @schema_rec) AS r; +r +0 +# Recursion through an anonymous type +SET @schema_rec= ' + + + + + + + + + + + +'; +# Expect 1 +SELECT XMLISVALID('xyz', @schema_rec) AS r; +r +1 +# Expect 0 +SELECT XMLISVALID('xz', @schema_rec) AS r; +r +0 +# Choice with an unfinished nested compositor +SET @schema_choice= ' + + + + + + + + + + +'; +# Expect 1 +SELECT XMLISVALID('12', @schema_choice) AS r; +r +1 +# Expect 1 +SELECT XMLISVALID('1', @schema_choice) AS r; +r +1 +# Expect 0, the chosen sequence misses +SELECT XMLISVALID('1', @schema_choice) AS r; +r +0 +# The same, with the choice nested in a sequence +SET @schema_choice= ' + + + + + + + + + + + + + +'; +# Expect 1 +SELECT XMLISVALID('123', @schema_choice) AS r; +r +1 +# Expect 0 +SELECT XMLISVALID('1', @schema_choice) AS r; +r +0 +# Expect 0 +SELECT XMLISVALID('13', @schema_choice) AS r; +r +0 +# Choice alternative is a group holding an incomplete sequence +SET @schema_choice= ' + + + + + + + + + + + + + +'; +# Expect 1 +SELECT XMLISVALID('12', @schema_choice) AS r; +r +1 +# Expect 0 +SELECT XMLISVALID('1', @schema_choice) AS r; +r +0 +# Choice with maxOccurs>1: an alternative that was tried but not chosen +# must not leave stale state, so the repeated alternative is accepted +SET @schema_choice= ' + + + + + + + + + + +'; +# Expect 1 +SELECT XMLISVALID('123', @schema_choice) AS r; +r +1 +# Expect 1 +SELECT XMLISVALID('123', @schema_choice) AS r; +r +1 +# Expect 1 +SELECT XMLISVALID('12', @schema_choice) AS r; +r +1 +# Expect 1 +SELECT XMLISVALID('123', @schema_choice) AS r; +r +1 +# Expect 1 +SELECT XMLISVALID('1234', @schema_choice) AS r; +r +1 +# Expect 0, incomplete sequence alternative +SELECT XMLISVALID('12', @schema_choice) AS r; +r +0 +# Expect 0, four occurrences +SELECT XMLISVALID('1234', @schema_choice) AS r; +r +0 +# +# An unbounded nested xs:sequence followed by another element +# (existing bug in XMLSchema_sequence::validate_tag_name: the nested +# sequence takes the tag that belongs to the following element). +# +SET @schema_nested= ' + + + + + + + + + + +'; +# Expect 1 +SELECT XMLISVALID('', @schema_nested) AS r; +r +1 +# Expect 1 +SELECT XMLISVALID('', @schema_nested) AS r; +r +1 +# Expect 0, the final element is missing +SELECT XMLISVALID('', @schema_nested) AS r; +r +0 +# Expect 0, incomplete nested sequence +SELECT XMLISVALID('', @schema_nested) AS r; +r +0 +# +# A nested xs:sequence followed by an optional element: the failed +# probe of a new iteration must not be counted as an occurrence. +# +SET @schema_nested_min= ' + + + + + + + + + + +'; +# Expect 1 +SELECT XMLISVALID('', @schema_nested_min) AS r; +r +1 +# Expect 1 +SELECT XMLISVALID('', @schema_nested_min) AS r; +r +1 +# Expect 0, only one occurrence of the nested sequence +SELECT XMLISVALID('', @schema_nested_min) AS r; +r +0 +# Expect 0, only one occurrence of the nested sequence, followed by c +SELECT XMLISVALID('', @schema_nested_min) AS r; +r +0 +# Expect 0, second occurrence is incomplete +SELECT XMLISVALID('', @schema_nested_min) AS r; +r +0 +SET @schema_nested_req= ' + + + + + + + + + +'; +# Expect 1 +SELECT XMLISVALID('', @schema_nested_req) AS r; +r +1 +# Expect 1 +SELECT XMLISVALID('', @schema_nested_req) AS r; +r +1 +# Expect 0, required nested sequence is missing +SELECT XMLISVALID('', @schema_nested_req) AS r; +r +0 +# Expect 0, required nested sequence is missing +SELECT XMLISVALID('', @schema_nested_req) AS r; +r +0 diff --git a/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.test b/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.test index fea3f9dd59b..61f1ba589e7 100644 --- a/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.test +++ b/plugin/type_xmltype/mysql-test/type_xmltype/type_xmltype_validation.test @@ -1079,7 +1079,7 @@ set @xml= ' 2026-06-01 '; -select XMLISVALID(@xml, @schema); +select XMLISVALID(@xml, @schema) Ex0; set @schema= ' @@ -1128,7 +1128,7 @@ set @xml= ' 2026-06-01 '; -select XMLISVALID(@xml, @schema); +select XMLISVALID(@xml, @schema) Ex1; set @schema= ' @@ -1175,7 +1175,7 @@ set @xml= ' 2026-06-01 '; -select XMLISVALID(@xml, @schema); +select XMLISVALID(@xml, @schema) Ex1; set @schema= ' @@ -2236,3 +2236,606 @@ SELECT XMLISVALID( 'x', ' ) AS r; +--echo +--echo MDEV-40394 XML schema fails on self-referencing type. +--echo + +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as V; + +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as Expect_1; + +SELECT XMLISVALID('', +' + + + + +') as V; + +SELECT XMLISVALID(' ', +' + + + + +') as V; + +SELECT XMLISVALID(' ', +' + + + + + + +') as V; + +--error ER_UNKNOWN_ERROR +SELECT XMLISVALID(' a ', + ' + + + + + + + + + + + + + + ') AS v; + +--error ER_UNKNOWN_ERROR +SELECT XMLISVALID(' a ', + ' + + + + + + + + + + + + + + + ') AS v; + + +# Case A: recursive xs:all, valid doc, inner element name reused at parent. +SELECT XMLISVALID('io', +' + + + + + ') as A_expect_1; + +# Case B: recursive xs:sequence, element maxOccurs=2, inner uses budget. +SELECT XMLISVALID('i1i2o1o2', +' + + + + + ') as B_expect_1; + +# Case C: recursive xs:all, does it FALSE-ACCEPT a duplicate? +SELECT XMLISVALID('ab', +' + + + + + ') as C_expect_0; + +SELECT XMLISVALID(' + + + + + One + + + + + Two + + + Three + + + + + + + + Four + + + + + ', +' + + + + + + + + +') as Expect_0; + +--echo # +--echo # Recursion through a shared xs:group +--echo # Every group_reference shares the group's single compositor, so a +--echo # nested use of the group resets the state of the outer one. +--echo # + +SET @schema_group= ' + + + + + + + + + +'; + +SET @schema_seq= ' + + + + + + + + + + + + + +'; + +--echo # Valid: nested holds its own , outer follows. Expect 1 +--echo # (same structure without the group, for comparison, is also 1) +SELECT XMLISVALID('xy', @schema_group) AS group_valid, + XMLISVALID('xy', @schema_seq) AS seq_valid; + +--echo # Valid, two levels deep. Expect 1 / 1 +SELECT XMLISVALID('xyz', @schema_group) AS group_valid, + XMLISVALID('xyz', @schema_seq) AS seq_valid; + +--echo # Invalid: outer missing. Expect 0 / 0 +SELECT XMLISVALID('x', @schema_group) AS group_invalid, + XMLISVALID('x', @schema_seq) AS seq_invalid; + +--echo # Invalid: inner missing. Expect 0 / 0 +SELECT XMLISVALID('y', @schema_group) AS group_invalid, + XMLISVALID('y', @schema_seq) AS seq_invalid; + + +--echo # +--echo # xs:group reference occurrence counting. +--echo # minOccurs/maxOccurs on a group reference, including an emptiable group. +--echo # + +SET @pre= ''; +SET @post= ' + + +'; + +--echo # Group with required content, minOccurs=2 maxOccurs=2 +SET @schema_group= CONCAT(@pre, ' + + + + + + ', @post); + +--echo # One occurrence: too few. Expect 0 +SELECT XMLISVALID('11x', @schema_group) AS grp; +--echo # Two occurrences. Expect 1 +SELECT XMLISVALID('1122x', @schema_group) AS grp; +--echo # Three occurrences: too many. Expect 0 +SELECT XMLISVALID('112233x', @schema_group) AS grp; +--echo # Second occurrence incomplete. Expect 0 +SELECT XMLISVALID('112x', @schema_group) AS grp; + +--echo # Group with minOccurs=0 +SET @schema_group= CONCAT(@pre, ' + + + + + + ', @post); + +--echo # Group absent. Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; +--echo # Group present. Expect 1 +SELECT XMLISVALID('11x', @schema_group) AS grp; +--echo # Group present twice (maxOccurs defaults to 1). Expect 0 +SELECT XMLISVALID('1122x', @schema_group) AS grp; +--echo # Group present but incomplete. Expect 0 +SELECT XMLISVALID('1x', @schema_group) AS grp; + +--echo # Group with minOccurs=1 maxOccurs=unbounded +SET @schema_group= CONCAT(@pre, ' + + + + + + ', @post); + +--echo # Absent: too few. Expect 0 +SELECT XMLISVALID('x', @schema_group) AS grp; +--echo # Four occurrences. Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; + +--echo # Emptiable group (all particles optional), default minOccurs=1 +SET @schema_group= CONCAT(@pre, ' + + + + + ', @post); + +--echo # Group content absent. Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; +--echo # Group content present. Expect 1 +SELECT XMLISVALID('1x', @schema_group) AS grp; + +--echo # Emptiable group, minOccurs=2 maxOccurs=3 +SET @schema_group= CONCAT(@pre, ' + + + + + ', @post); + +--echo # Group content absent. Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; +--echo # Three . Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; +--echo # Four : too many. Expect 0 +SELECT XMLISVALID('x', @schema_group) AS grp; + +--echo # Emptiable group reference as the last particle of a sequence +SET @schema_group= CONCAT(@pre, ' + + + + + + + + +'); + +--echo # Group content absent, no tag reaches the group reference. Expect 1 +SELECT XMLISVALID('x', @schema_group) AS grp; +--echo # Group content present. Expect 1 +SELECT XMLISVALID('x1', @schema_group) AS grp; +--echo # Required missing. Expect 0 +SELECT XMLISVALID('1', @schema_group) AS grp; + +--echo # +--echo # recursion depth limit and recovery after aborted validation +--echo # + +SET @schema_rec= ' + + + + + + + +'; + +--echo # Just below the depth limit. Expect 1 +SELECT XMLISVALID(CONCAT('', REPEAT('', 1021), 'x', + REPEAT('', 1021), ''), @schema_rec) AS below_limit; +--error ER_UNKNOWN_ERROR +SELECT XMLISVALID(CONCAT('', REPEAT('', 1100), 'x', + REPEAT('', 1100), ''), @schema_rec) AS over_limit; + +--echo # Rows after an aborted validation are not affected by it +CREATE TABLE t1 (id INT, x LONGTEXT); +INSERT INTO t1 VALUES + (1, ''), + (2, 'x'), + (3, 'x'), + (4, 'x'), + (5, ''), + (6, 'x'); +SELECT id, XMLISVALID(x, @schema_rec) AS r FROM t1 ORDER BY id; +DROP TABLE t1; + +--echo # Recursion through xs:choice +SET @schema_rec= ' + + + + + + + +'; +--echo # Expect 1 +SELECT XMLISVALID('a', @schema_rec) AS r; +--echo # Expect 0 +SELECT XMLISVALID('ab', @schema_rec) AS r; +--echo # Expect 0 +SELECT XMLISVALID('', @schema_rec) AS r; + +--echo # Recursion through an anonymous type +SET @schema_rec= ' + + + + + + + + + + + +'; +--echo # Expect 1 +SELECT XMLISVALID('xyz', @schema_rec) AS r; +--echo # Expect 0 +SELECT XMLISVALID('xz', @schema_rec) AS r; + +--echo # Choice with an unfinished nested compositor +SET @schema_choice= ' + + + + + + + + + + +'; +--echo # Expect 1 +SELECT XMLISVALID('12', @schema_choice) AS r; +--echo # Expect 1 +SELECT XMLISVALID('1', @schema_choice) AS r; +--echo # Expect 0, the chosen sequence misses +SELECT XMLISVALID('1', @schema_choice) AS r; + +--echo # The same, with the choice nested in a sequence +SET @schema_choice= ' + + + + + + + + + + + + + +'; +--echo # Expect 1 +SELECT XMLISVALID('123', @schema_choice) AS r; +--echo # Expect 0 +SELECT XMLISVALID('1', @schema_choice) AS r; +--echo # Expect 0 +SELECT XMLISVALID('13', @schema_choice) AS r; + +--echo # Choice alternative is a group holding an incomplete sequence +SET @schema_choice= ' + + + + + + + + + + + + + +'; +--echo # Expect 1 +SELECT XMLISVALID('12', @schema_choice) AS r; +--echo # Expect 0 +SELECT XMLISVALID('1', @schema_choice) AS r; + +--echo # Choice with maxOccurs>1: an alternative that was tried but not chosen +--echo # must not leave stale state, so the repeated alternative is accepted +SET @schema_choice= ' + + + + + + + + + + +'; +--echo # Expect 1 +SELECT XMLISVALID('123', @schema_choice) AS r; +--echo # Expect 1 +SELECT XMLISVALID('123', @schema_choice) AS r; +--echo # Expect 1 +SELECT XMLISVALID('12', @schema_choice) AS r; +--echo # Expect 1 +SELECT XMLISVALID('123', @schema_choice) AS r; +--echo # Expect 1 +SELECT XMLISVALID('1234', @schema_choice) AS r; +--echo # Expect 0, incomplete sequence alternative +SELECT XMLISVALID('12', @schema_choice) AS r; +--echo # Expect 0, four occurrences +SELECT XMLISVALID('1234', @schema_choice) AS r; + +--echo # +--echo # An unbounded nested xs:sequence followed by another element +--echo # (existing bug in XMLSchema_sequence::validate_tag_name: the nested +--echo # sequence takes the tag that belongs to the following element). +--echo # +SET @schema_nested= ' + + + + + + + + + + +'; +--echo # Expect 1 +SELECT XMLISVALID('', @schema_nested) AS r; +--echo # Expect 1 +SELECT XMLISVALID('', @schema_nested) AS r; +--echo # Expect 0, the final element is missing +SELECT XMLISVALID('', @schema_nested) AS r; +--echo # Expect 0, incomplete nested sequence +SELECT XMLISVALID('', @schema_nested) AS r; + +--echo # +--echo # A nested xs:sequence followed by an optional element: the failed +--echo # probe of a new iteration must not be counted as an occurrence. +--echo # +SET @schema_nested_min= ' + + + + + + + + + + +'; +--echo # Expect 1 +SELECT XMLISVALID('', @schema_nested_min) AS r; +--echo # Expect 1 +SELECT XMLISVALID('', @schema_nested_min) AS r; +--echo # Expect 0, only one occurrence of the nested sequence +SELECT XMLISVALID('', @schema_nested_min) AS r; +--echo # Expect 0, only one occurrence of the nested sequence, followed by c +SELECT XMLISVALID('', @schema_nested_min) AS r; +--echo # Expect 0, second occurrence is incomplete +SELECT XMLISVALID('', @schema_nested_min) AS r; + +SET @schema_nested_req= ' + + + + + + + + + +'; +--echo # Expect 1 +SELECT XMLISVALID('', @schema_nested_req) AS r; +--echo # Expect 1 +SELECT XMLISVALID('', @schema_nested_req) AS r; +--echo # Expect 0, required nested sequence is missing +SELECT XMLISVALID('', @schema_nested_req) AS r; +--echo # Expect 0, required nested sequence is missing +SELECT XMLISVALID('', @schema_nested_req) AS r;