List:Commits« Previous MessageNext Message »
From:bar Date:June 27 2007 7:04am
Subject:bk commit into 5.2 tree (bar:1.2527)
View as plain text  
Below is the list of changes that have just been committed into a local
5.2 repository of bar. When bar does a push these changes will
be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet@stripped, 2007-06-27 12:04:18+05:00, bar@stripped +7 -0
  WL#1397 convert XML -> SQL
  A contributed patch from Erik Wetterberg
  implementing "LOAD XML INFILE".

  mysql-test/r/loadxml.result@stripped, 2007-06-27 12:04:16+05:00, bar@stripped +36 -0
    Adding tests

  mysql-test/r/loadxml.result@stripped, 2007-06-27 12:04:16+05:00, bar@stripped +0 -0

  mysql-test/std_data/loadxml.dat@stripped, 2007-06-27 12:04:16+05:00, bar@stripped +45 -0
    Adding test XML file

  mysql-test/std_data/loadxml.dat@stripped, 2007-06-27 12:04:16+05:00, bar@stripped +0 -0

  mysql-test/t/loadxml.test@stripped, 2007-06-27 12:04:16+05:00, bar@stripped +41 -0
    Adding tests

  mysql-test/t/loadxml.test@stripped, 2007-06-27 12:04:16+05:00, bar@stripped +0 -0

  sql/lex.h@stripped, 2007-06-27 12:04:15+05:00, bar@stripped +1 -0
    Adding a new non-reserved keyword

  sql/sql_class.h@stripped, 2007-06-27 12:04:15+05:00, bar@stripped +2 -0
    Declaration of new enum, to distinguish CSV and XML file types

  sql/sql_load.cc@stripped, 2007-06-27 12:04:15+05:00, bar@stripped +504 -2
    Adding load xml functionality

  sql/sql_yacc.yy@stripped, 2007-06-27 12:04:16+05:00, bar@stripped +26 -4
    Adding LOAD XML syntax

# This is a BitKeeper patch.  What follows are the unified diffs for the
# set of deltas contained in the patch.  The rest of the patch, the part
# that BitKeeper cares about, is below these diffs.
# User:	bar
# Host:	bar.myoffice.izhnet.ru
# Root:	/home/bar/mysql-work/mysql-5.2.wl1397

--- 1.174/sql/lex.h	2007-06-18 16:40:21 +05:00
+++ 1.175/sql/lex.h	2007-06-27 12:04:15 +05:00
@@ -582,6 +582,7 @@ static SYMBOL symbols[] = {
   { "X509",		SYM(X509_SYM)},
   { "XOR",		SYM(XOR)},
   { "XA",               SYM(XA_SYM)},
+  { "XML",              SYM(XML_SYM)}, /* LOAD XML Arnold/Erik */
   { "YEAR",		SYM(YEAR_SYM)},
   { "YEAR_MONTH",	SYM(YEAR_MONTH_SYM)},
   { "ZEROFILL",		SYM(ZEROFILL)},

--- 1.368/sql/sql_class.h	2007-06-12 18:10:59 +05:00
+++ 1.369/sql/sql_class.h	2007-06-27 12:04:15 +05:00
@@ -43,6 +43,7 @@ enum enum_check_fields
 { CHECK_FIELD_IGNORE, CHECK_FIELD_WARN, CHECK_FIELD_ERROR_FOR_NULL };
 enum enum_mark_columns
 { MARK_COLUMNS_NONE, MARK_COLUMNS_READ, MARK_COLUMNS_WRITE};
+enum enum_filetype { FILETYPE_CSV, FILETYPE_XML };
 
 extern char internal_table_name[2];
 extern char empty_c_string[1];
@@ -1821,6 +1822,7 @@ private:
 class sql_exchange :public Sql_alloc
 {
 public:
+  enum enum_filetype filetype; /* load XML, Added by Arnold & Erik */ 
   char *file_name;
   String *field_term,*enclosed,*line_term,*line_start,*escaped;
   bool opt_enclosed;

--- 1.129/sql/sql_load.cc	2007-06-20 19:41:31 +05:00
+++ 1.130/sql/sql_load.cc	2007-06-27 12:04:15 +05:00
@@ -15,6 +15,7 @@
 
 
 /* Copy data from a textfile to table */
+/* 2006-12 Erik Wetterberg : LOAD XML added */
 
 #include "mysql_priv.h"
 #include <my_dir.h>
@@ -23,6 +24,23 @@
 #include "sp_head.h"
 #include "sql_trigger.h"
 
+class XML_TAG {
+public:
+  int level;
+  String field;
+  String value;
+  XML_TAG(int l, String f, String v);
+};
+
+
+XML_TAG::XML_TAG(int l, String f, String v)
+{
+  level= l;
+  field.append(f);
+  value.append(v);
+}
+
+
 class READ_INFO {
   File	file;
   uchar	*buffer,			/* Buffer for read text */
@@ -37,6 +55,7 @@ class READ_INFO {
   bool  need_end_io_cache;
   IO_CACHE cache;
   NET *io_net;
+  int level; /* for load xml */
 
 public:
   bool error,line_cuted,found_null,enclosed;
@@ -54,6 +73,12 @@ public:
   char unescape(char chr);
   int terminator(char *ptr,uint length);
   bool find_start_of_fields();
+  /* load xml */
+  List<XML_TAG> taglist;
+  int read_value(int delim, String *val);
+  int read_xml();
+  int clear_level(int level);
+
   /*
     We need to force cache close before destructor is invoked to log
     the last read block
@@ -82,6 +107,13 @@ static int read_sep_field(THD *thd, COPY
                           List<Item> &set_values, READ_INFO &read_info,
 			  String &enclosed, ulong skip_lines,
 			  bool ignore_check_option_errors);
+
+static int read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
+                          List<Item> &fields_vars, List<Item> &set_fields,
+                          List<Item> &set_values, READ_INFO &read_info,
+                          String &enclosed, ulong skip_lines,
+                          bool ignore_check_option_errors);
+
 #ifndef EMBEDDED_LIBRARY
 static bool write_execute_load_query_log_event(THD *thd,
 					       bool duplicates, bool ignore,
@@ -350,7 +382,7 @@ bool mysql_load(THD *thd,sql_exchange *e
   thd->count_cuted_fields= CHECK_FIELD_WARN;		/* calc cuted fields */
   thd->cuted_fields=0L;
   /* Skip lines if there is a line terminator */
-  if (ex->line_term->length())
+  if (ex->line_term->length() && ex->filetype != FILETYPE_XML)
   {
     /* ex->skip_lines needs to be preserved for logging */
     while (skip_lines > 0)
@@ -382,7 +414,11 @@ bool mysql_load(THD *thd,sql_exchange *e
                              (MODE_STRICT_TRANS_TABLES |
                               MODE_STRICT_ALL_TABLES)));
 
-    if (!field_term->length() && !enclosed->length())
+    if (ex->filetype == FILETYPE_XML) /* load xml */
+      error= read_xml_field(thd, info, table_list, fields_vars,
+                            set_fields, set_values, read_info,
+                            *(ex->line_term), skip_lines, ignore);
+    else if (!field_term->length() && !enclosed->length())
       error= read_fixed_length(thd, info, table_list, fields_vars,
                                set_fields, set_values, read_info,
 			       skip_lines, ignore);
@@ -846,6 +882,170 @@ continue_loop:;
 }
 
 
+/****************************************************************************
+** Read rows in xml format
+****************************************************************************/
+static int
+read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
+               List<Item> &fields_vars, List<Item> &set_fields,
+               List<Item> &set_values, READ_INFO &read_info,
+               String &row_tag, ulong skip_lines,
+               bool ignore_check_option_errors)
+{
+  List_iterator_fast<Item> it(fields_vars);
+  Item *item;
+  TABLE *table= table_list->table;
+  bool no_trans_update_stmt;
+  CHARSET_INFO *cs= read_info.read_charset;
+  DBUG_ENTER("read_xml_field");
+  
+  no_trans_update_stmt= !table->file->has_transactions();
+  
+  for ( ; ; it.rewind())
+  {
+    if (thd->killed)
+    {
+      thd->send_kill_message();
+      DBUG_RETURN(1);
+    }
+    
+    // read row tag and save values into tag list
+    if (read_info.read_xml())
+      break;
+    
+    List_iterator_fast<XML_TAG> xmlit(read_info.taglist);
+    xmlit.rewind();
+    XML_TAG *tag= NULL;
+    
+#ifndef DBUG_OFF
+    DBUG_PRINT("read_xml_field", ("skip_lines=%d", (int) skip_lines));
+    while ((tag= xmlit++))
+    {
+      DBUG_PRINT("read_xml_field", ("got tag:%i '%s' '%s'",
+                                    tag->level, tag->field.c_ptr(),
+                                    tag->value.c_ptr()));
+    }
+#endif
+    
+    restore_record(table, s->default_values);
+    
+    while ((item= it++))
+    {
+      /* If this line is to be skipped we don't want to fill field or var */
+      if (skip_lines)
+        continue;
+      
+      /* find field in tag list */
+      xmlit.rewind();
+      tag= xmlit++;
+      
+      while(tag && strcmp(tag->field.c_ptr(), item->name) != 0)
+        tag= xmlit++;
+      
+      if (!tag) // found null
+      {
+        if (item->type() == Item::FIELD_ITEM)
+        {
+          Field *field= ((Item_field *) item)->field;
+          field->reset();
+          field->set_null();
+          if (field == table->next_number_field)
+            table->auto_increment_field_not_null= TRUE;
+          if (!field->maybe_null())
+          {
+            if (field->type() == FIELD_TYPE_TIMESTAMP)
+              ((Field_timestamp *) field)->set_time();
+            else if (field != table->next_number_field)
+              field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN,
+                                 ER_WARN_NULL_TO_NOTNULL, 1);
+          }
+        }
+        else
+          ((Item_user_var_as_out_param *) item)->set_null_value(cs);
+        continue;
+      }
+
+      if (item->type() == Item::FIELD_ITEM)
+      {
+
+        Field *field= ((Item_field *)item)->field;
+        field->set_notnull();
+        if (field == table->next_number_field)
+          table->auto_increment_field_not_null= TRUE;
+        field->store((char *) tag->value.ptr(), tag->value.length(), cs);
+      }
+      else
+        ((Item_user_var_as_out_param *) item)->set_value(
+                                                 (char *) tag->value.ptr(), 
+                                                 tag->value.length(), cs);
+    }
+    
+    if (read_info.error)
+      break;
+    
+    if (skip_lines)
+    {
+      skip_lines--;
+      continue;
+    }
+    
+    if (item)
+    {
+      /* Have not read any field, thus input file is simply ended */
+      if (item == fields_vars.head())
+        break;
+      
+      for ( ; item; item= it++)
+      {
+        if (item->type() == Item::FIELD_ITEM)
+        {
+          /*
+            QQ: We probably should not throw warning for each field.
+            But how about intention to always have the same number
+            of warnings in THD::cuted_fields (and get rid of cuted_fields
+            in the end ?)
+          */
+          thd->cuted_fields++;
+          push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+                              ER_WARN_TOO_FEW_RECORDS,
+                              ER(ER_WARN_TOO_FEW_RECORDS), thd->row_count);
+        }
+        else
+          ((Item_user_var_as_out_param *)item)->set_null_value(cs);
+      }
+    }
+
+    if (thd->killed ||
+        fill_record_n_invoke_before_triggers(thd, set_fields, set_values,
+                                             ignore_check_option_errors,
+                                             table->triggers,
+                                             TRG_EVENT_INSERT))
+      DBUG_RETURN(1);
+
+    switch (table_list->view_check_option(thd,
+                                          ignore_check_option_errors)) {
+    case VIEW_CHECK_SKIP:
+      read_info.next_line();
+      goto continue_loop;
+    case VIEW_CHECK_ERROR:
+      DBUG_RETURN(-1);
+    }
+    
+    if (write_record(thd, table, &info))
+      DBUG_RETURN(1);
+    
+    /*
+      We don't need to reset auto-increment field since we are restoring
+      its default value at the beginning of each loop iteration.
+    */
+    thd->no_trans_update.stmt= no_trans_update_stmt;
+    thd->row_count++;
+    continue_loop:;
+  }
+  DBUG_RETURN(test(read_info.error));
+} /* load xml end */
+
+
 /* Unescape all escape characters, mark \N as null */
 
 char
@@ -883,6 +1083,7 @@ READ_INFO::READ_INFO(File file_par, uint
   field_term_length= field_term.length();
   line_term_ptr=(char*) line_term.ptr();
   line_term_length= line_term.length();
+  level= 0; /* for load xml */
   if (line_start.length() == 0)
   {
     line_start_ptr=0;
@@ -958,6 +1159,10 @@ READ_INFO::~READ_INFO()
     my_free((uchar*) buffer,MYF(0));
     error=1;
   }
+  List_iterator<XML_TAG> xmlit(taglist);
+  XML_TAG *t;
+  while ((t= xmlit++))
+    delete(t);
 }
 
 
@@ -1289,4 +1494,301 @@ bool READ_INFO::find_start_of_fields()
     }
   }
   return 0;
+}
+
+
+/*
+  Clear taglist from tags with a specified level
+*/
+int READ_INFO::clear_level(int level)
+{
+  DBUG_ENTER("READ_INFO::read_xml clear_level");
+  List_iterator<XML_TAG> xmlit(taglist);
+  xmlit.rewind();
+  XML_TAG *tag;
+  
+  while ((tag= xmlit++))
+  {
+     if(tag->level >= level)
+     {
+       xmlit.remove();
+       delete tag;
+     }
+  }
+  DBUG_RETURN(0);
+}
+
+
+/*
+  Convert an XML entity to Unicode value.
+  Return -1 on error;
+*/
+static int
+my_xml_entity_to_char(const char *name, uint length)
+{
+  if (length == 2)
+  {
+    if (!memcmp(name, "gt", length))
+      return '>';
+    if (!memcmp(name, "lt", length))
+      return '<';
+  }
+  else if (length == 3)
+  {
+    if (!memcmp(name, "amp", length))
+      return '&';
+  }
+  else if (length == 4)
+  {
+    if (!memcmp(name, "quot", length))
+      return '"';
+    if (!memcmp(name, "apos", length))
+      return '\'';
+  }
+  return -1;
+}
+
+
+/*
+  Read an xml value: handle multibyte and xml escape
+*/
+int READ_INFO::read_value(int delim, String *val)
+{
+  int chr;
+  String tmp;
+
+  for (chr= GET; chr != delim && chr != my_b_EOF; )
+  {
+#ifdef USE_MB
+    if (my_mbcharlen(read_charset, chr) > 1)
+    {
+      DBUG_PRINT("read_xml",("multi byte"));
+      int i, ml= my_mbcharlen(read_charset, chr);
+      for (i= 1; i < ml; i++) 
+      {
+        val->append(chr);
+        chr= GET;
+        if (chr == my_b_EOF)
+          return chr;
+      }
+    }
+#endif
+    if(my_isspace(read_charset, chr)) /* convert newline, tab etc to space */
+      val->append(' ');
+    else if(chr == '&')
+    {
+      tmp.length(0);
+      for (chr= GET ; chr != ';' ; chr= GET)
+      {
+        if (chr == my_b_EOF)
+          return chr;
+        tmp.append(chr);
+      }
+      if ((chr= my_xml_entity_to_char(tmp.ptr(), tmp.length())) >= 0)
+        val->append(chr);
+      else
+      {
+        val->append('&');
+        val->append(tmp);
+        val->append(';'); 
+      }
+    }
+    else
+      val->append(chr);
+    chr= GET; 
+  }            
+  return chr;
+}
+
+
+/*
+  Read a record in xml format
+  tags and attributes are stored in taglist
+  when tag set in ROWS IDENTIFIED BY is closed, we are ready and return
+*/
+int READ_INFO::read_xml()
+{
+  DBUG_ENTER("READ_INFO::read_xml");
+  int chr, chr2, chr3;
+  int delim= 0;
+  String tag, attribute, value;
+  bool in_tag= false;
+  
+  tag.length(0);
+  attribute.length(0);
+  value.length(0);
+  
+  for (chr= GET; chr != my_b_EOF ; )
+  {
+    switch(chr){
+    case '<':  /* read tag */
+        /* TODO: check if this is a comment <!-- comment -->  */
+      chr= GET;
+      if(chr == '!')
+      {
+        chr2= GET;
+        chr3= GET;
+        
+        if(chr2 == '-' && chr3 == '-')
+        {
+          chr2= 0;
+          chr3= 0;
+          chr= GET;
+          
+          while(chr != '>' || chr2 != '-' || chr3 != '-')
+          {
+            if(chr == '-')
+            {
+              chr3= chr2;
+              chr2= chr;
+            }
+            else if (chr2 == '-')
+            {
+              chr2= 0;
+              chr3= 0;
+            }
+            chr= GET;
+            if (chr == my_b_EOF)
+              goto found_eof;
+          }
+          break;
+        }
+      }
+      
+      tag.length(0);
+      while(chr != '>' && chr != ' ' && chr != '/' && chr != my_b_EOF)
+      {
+        if(chr != delim) /* fix for the '<field name =' format */
+          tag.append(chr);
+        chr= GET;
+      }
+      
+      // row tag should be in ROWS IDENTIFIED BY '<row>' - stored in line_term 
+      if((tag.length() == line_term_length -2) &&
+         (strncmp(tag.c_ptr_safe(), line_term_ptr + 1, tag.length()) == 0))
+      {
+        DBUG_PRINT("read_xml", ("start-of-row: %i %s %s", 
+                                level,tag.c_ptr_safe(), line_term_ptr));
+      }
+      
+      if(chr == ' ' || chr == '>')
+      {
+        level++;
+        clear_level(level + 1);
+      }
+      
+      if (chr == ' ')
+        in_tag= true;
+      else 
+        in_tag= false;
+      break;
+      
+    case ' ': /* read attribute */
+      while(chr == ' ')  /* skip blanks */
+        chr= GET;
+      
+      if(!in_tag)
+        break;
+      
+      while(chr != '=' && chr != '/' && chr != '>' && chr != my_b_EOF)
+      {
+        attribute.append(chr);
+        chr= GET;
+      }
+      break;
+      
+    case '>': /* end tag - read tag value */
+      in_tag= false;
+      chr= read_value('<', &value);
+      if(chr == my_b_EOF)
+        goto found_eof;
+      
+      /* save value to list */
+      if(tag.length() > 0 && value.length() > 0)
+      {
+        DBUG_PRINT("read_xml", ("lev:%i tag:%s val:%s",
+                                level,tag.c_ptr_safe(), value.c_ptr_safe()));
+        taglist.push_front( new XML_TAG(level, tag, value));
+      }
+      tag.length(0);
+      value.length(0);
+      attribute.length(0);
+      break;
+      
+    case '/': /* close tag */
+      level--;
+      chr= GET;
+      if(chr != '>')   /* if this is an empty tag <tag   /> */
+        tag.length(0); /* we should keep tag value          */
+      while(chr != '>' && chr != my_b_EOF)
+      {
+        tag.append(chr);
+        chr= GET;
+      }
+      
+      if((tag.length() == line_term_length -2) &&
+         (strncmp(tag.c_ptr_safe(), line_term_ptr + 1, tag.length()) == 0))
+      {
+         DBUG_PRINT("read_xml", ("found end-of-row %i %s", 
+                                 level, tag.c_ptr_safe()));
+         DBUG_RETURN(0); //normal return
+      }
+      chr= GET;
+      break;   
+      
+    case '=': /* attribute name end - read the value */
+      //check for tag field and attribute name
+      if(!memcmp(tag.c_ptr_safe(), STRING_WITH_LEN("field")) &&
+         !memcmp(attribute.c_ptr_safe(), STRING_WITH_LEN("name")))
+      {
+        /*
+          this is format <field name="xx">xx</field>
+          where actual fieldname is in attribute
+        */
+        delim= GET;
+        tag.length(0);
+        attribute.length(0);
+        chr= '<'; /* we pretend that it is a tag */
+        level--;
+        break;
+      }
+      
+      //check for " or '
+      chr= GET;
+      if (chr == my_b_EOF)
+        goto found_eof;
+      if(chr == '"' || chr == '\'')
+      {
+        delim= chr;
+      }
+      else
+      {
+        delim= ' '; /* no delimiter, use space */
+        PUSH(chr);
+      }
+      
+      chr= read_value(delim, &value);
+      if(attribute.length() > 0 && value.length() > 0)
+      {
+        DBUG_PRINT("read_xml", ("lev:%i att:%s val:%s\n",
+                                level + 1,
+                                attribute.c_ptr_safe(),
+                                value.c_ptr_safe()));
+        taglist.push_front(new XML_TAG(level + 1, attribute, value));
+      }
+      attribute.length(0);
+      value.length(0);
+      if (chr != ' ')
+        chr= GET;
+      break;
+    
+    default:
+      chr= GET;  
+    } /* end switch */
+  } /* end while */
+  
+found_eof:
+  DBUG_PRINT("read_xml",("Found eof"));
+  eof= 1;
+  DBUG_RETURN(1);
 }

--- 1.580/sql/sql_yacc.yy	2007-06-21 13:16:02 +05:00
+++ 1.581/sql/sql_yacc.yy	2007-06-27 12:04:16 +05:00
@@ -484,6 +484,7 @@ Item* handle_sql2003_note184_exception(T
   sp_head *sphead;
   struct p_elem_val *p_elem_value;
   enum index_hint_type index_hint;
+  enum enum_filetype filetype;
 }
 
 %{
@@ -1067,6 +1068,7 @@ bool my_yyoverflow(short **a, YYSTYPE **
 %token  WRITE_SYM                     /* SQL-2003-N */
 %token  X509_SYM
 %token  XA_SYM
+%token  XML_SYM
 %token  XOR
 %token  YEAR_MONTH_SYM
 %token  YEAR_SYM                      /* SQL-2003-R */
@@ -1288,6 +1290,7 @@ END_OF_INPUT
 %type <spname> sp_name
 %type <index_hint> index_hint_type
 %type <num> index_hint_clause
+%type <filetype> data_or_xml
 
 %type <NONE>
 	'-' '+' '*' '/' '%' '(' ')'
@@ -9125,7 +9128,7 @@ use:	USE_SYM ident
 
 /* import, export of files */
 
-load:   LOAD DATA_SYM
+load:   LOAD data_or_xml
         {
           THD *thd= YYTHD;
           LEX *lex= thd->lex;
@@ -9133,7 +9136,8 @@ load:   LOAD DATA_SYM
 
 	  if (lex->sphead)
 	  {
-	    my_error(ER_SP_BADSTATEMENT, MYF(0), "LOAD DATA");
+	    my_error(ER_SP_BADSTATEMENT, MYF(0), 
+                     $2 == FILETYPE_CSV ? "LOAD DATA" : "LOAD XML");
 	    MYSQL_YYABORT;
 	  }
           lex->fname_start= lip->ptr;
@@ -9148,6 +9152,7 @@ load:   LOAD DATA_SYM
 	  lex->ignore= 0;
 	  if (!(lex->exchange= new sql_exchange($7.str, 0)))
 	    MYSQL_YYABORT;
+          lex->exchange->filetype= $2;
         }
         opt_duplicate INTO
         {
@@ -9167,12 +9172,18 @@ load:   LOAD DATA_SYM
           lex->value_list.empty();
         }
         opt_load_data_charset
-	{ Lex->exchange->cs= $15; }
+        { Lex->exchange->cs= $15; }
+        opt_xml_rows_identified_by
         opt_field_term opt_line_term opt_ignore_lines opt_field_or_var_spec
         opt_load_data_set_spec
         {}
         ;
 
+data_or_xml:
+        DATA_SYM  { $$= FILETYPE_CSV; }
+        | XML_SYM { $$= FILETYPE_XML; }
+        ;
+
 opt_local:
 	/* empty */	{ $$=0;}
 	| LOCAL_SYM	{ $$=1;};
@@ -9251,14 +9262,24 @@ line_term:
             Lex->exchange->line_start= $3;
           };
 
+opt_xml_rows_identified_by:
+        /* empty */ { }
+        | ROWS_SYM IDENTIFIED_SYM BY text_string
+          { Lex->exchange->line_term = $4; };
+
 opt_ignore_lines:
 	/* empty */
-        | IGNORE_SYM NUM LINES
+        | IGNORE_SYM NUM lines_or_rows
           {
             DBUG_ASSERT(Lex->exchange != 0);
             Lex->exchange->skip_lines= atol($2.str);
           };
 
+lines_or_rows:
+        LINES { }
+        | ROWS_SYM { }
+        ;
+
 opt_field_or_var_spec:
 	/* empty */	          { }
 	| '(' fields_or_vars ')'  { }
@@ -10095,6 +10116,7 @@ keyword_sp:
 	| WORK_SYM		{}
 	| X509_SYM		{}
 	| YEAR_SYM		{}
+        | XML_SYM               {}
 	;
 
 /* Option functions */
--- New file ---
+++ mysql-test/r/loadxml.result	07/06/27 12:04:16
drop table if exists t1;
create table t1 (a int, b varchar(64));
-- Load a static XML file
load xml infile '../std_data_ln/loadxml.dat' into table t1
rows identified by '<row>';
select * from t1 order by a;
a	b
1	b1
2	b2
3	b3
11	b11
111	b111
112	b112 & < > " ' &unknown; -- check entities
212	b212
delete from t1;
-- Load a static XML file with 'IGNORE num ROWS'
load xml infile '../std_data_ln/loadxml.dat' into table t1
rows identified by '<row>' ignore 4 rows;
select * from t1 order by a;
a	b
111	b111
112	b112 & < > " ' &unknown; -- check entities
212	b212
-- Check 'mysqldump --xml' + 'LOAD XML' round trip
delete from t1;
load xml infile 'MYSQL_TMP_DIR/loadxml-dump.xml' into table t1 rows identified by '<row>';;
select * from t1 order by a;
a	b
111	b111
112	b112 & < > " ' &unknown; -- check entities
212	b212
-- Check that 'xml' is not a keyword
select 1 as xml;
xml
1
drop table t1;

--- New file ---
+++ mysql-test/std_data/loadxml.dat	07/06/27 12:04:16
<?xml version="1.0"?>
<mysqldump xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<database name="test">
	<table_structure name="t1">
		<field Field="a" Type="int(11)" Null="YES" Key="" Extra="" />
		<field Field="b" Type="varchar(128)" Null="YES" Key="" Extra="" />
		<options Name="t1" Engine="MyISAM" Version="10" Row_format="Dynamic" Rows="3" Avg_row_length="20" Data_length="60" Max_data_length="281474976710655" Index_length="1024" Data_free="0" Create_time="2007-02-09 09:08:36" Update_time="2007-02-09 09:08:54" Collation="latin1_swedish_ci" Create_options="" Comment="" />
	</table_structure>
	<table_data name="t1">
	<row>
		<field name="a">1</field>
		<field name="b">b1</field>
	</row>
	<row>
		<field name="a">2</field>
		<field name="b">b2</field>
	</row>
	<row>
		<field name="a">3</field>
		<field name="b">b3</field>
	</row>
	<row>
		<field name="a">11</field>
		<field name="b">b11</field>
	</row>
	
	<!-- Check field values as tags -->
	<row>
		<a>111</a>
		<b>b111</b>
	</row>

	<row>
		<a>112</a>
		<b>b112 &amp; &lt; &gt; &quot; &apos; &unknown; -- check entities</b>
	</row>


	<!-- Check field values in attributes -->
	<row a=212 b="b212"</row>

	</table_data>
</database>
</mysqldump>


--- New file ---
+++ mysql-test/t/loadxml.test	07/06/27 12:04:16
#
# Tests for "LOAD XML" - a contributed patch from Erik Wetterberg.
#

--disable_warnings
drop table if exists t1;
--enable_warnings

create table t1 (a int, b varchar(64));


--echo -- Load a static XML file
load xml infile '../std_data_ln/loadxml.dat' into table t1
rows identified by '<row>';
select * from t1 order by a;
delete from t1;


--echo -- Load a static XML file with 'IGNORE num ROWS'
load xml infile '../std_data_ln/loadxml.dat' into table t1
rows identified by '<row>' ignore 4 rows;
select * from t1 order by a;


--echo -- Check 'mysqldump --xml' + 'LOAD XML' round trip
--exec $MYSQL_DUMP --xml test t1 > "$MYSQL_TMP_DIR/loadxml-dump.xml" 2>&1
delete from t1;
--replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR
--eval load xml infile '$MYSQL_TMP_DIR/loadxml-dump.xml' into table t1 rows identified by '<row>';
select * from t1 order by a;

--echo -- Check that 'xml' is not a keyword
select 1 as xml;


#
# Clean up
#
--system rm $MYSQL_TMP_DIR/loadxml-dump.xml
drop table t1;


Thread
bk commit into 5.2 tree (bar:1.2527)bar27 Jun