Commit 9cd5f49c authored by kaa@polly.(none)'s avatar kaa@polly.(none)
Browse files

Fix for:

  bug #26215: mysql command line client should not strip comments
              from SQL statements
and
  bug #11230: Keeping comments when storing stored procedures

With the introduction of multiline comments support in the command line
client (mysql) in MySQL 4.1, it became impossible to preserve
client-side comments within single SQL statements or stored routines.
This feature was useful for monitoring tools and maintenance.

The patch adds a new option to the command line client
('--enable-comments', '-c') which allows to preserve SQL comments and
send them to the server for single SQL statements, and to keep comments
in the code for stored procedures / functions / triggers.

The patch is a modification of the contributed patch from bug #11230
with the following changes:
- code style changes to conform to the coding guidelines
- changed is_prefix() to my_strnncoll() to detect the DELIMITER
command, since the first one is case-sensitive and not charset-aware
- renamed t/comments-51.* to t/mysql_comments.*
- removed tests for comments in triggers since 5.0 does not have SHOW
CREATE TRIGGER (those tests will be added back in 5.1).

The test cases are only for bug #11230. No automated test case for bug
#26215 is possible due to the test suite deficiencies (though the cases
from the bug report were tested manually).
parent 34984111
Loading
Loading
Loading
Loading
+125 −43
Original line number Diff line number Diff line
@@ -140,6 +140,7 @@ static my_bool info_flag=0,ignore_errors=0,wait_flag=0,quick=0,
               default_pager_set= 0, opt_sigint_ignore= 0,
               show_warnings= 0;
static volatile int executing_query= 0, interrupted_query= 0;
static my_bool preserve_comments= 0;
static ulong opt_max_allowed_packet, opt_net_buffer_length;
static uint verbose=0,opt_silent=0,opt_mysql_port=0, opt_local_infile=0;
static my_string opt_mysql_unix_port=0;
@@ -754,6 +755,10 @@ static struct my_option my_long_options[] =
  {"show-warnings", OPT_SHOW_WARNINGS, "Show warnings after every statement.",
    (gptr*) &show_warnings, (gptr*) &show_warnings, 0, GET_BOOL, NO_ARG, 
    0, 0, 0, 0, 0, 0},
  {"comments", 'c', "Preserve comments. Send comments to the server."
   " Comments are discarded by default, enable with --enable-comments",
   (gptr*) &preserve_comments, (gptr*) &preserve_comments,
   0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
  { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};

@@ -1131,10 +1136,6 @@ static int read_and_execute(bool interactive)
      status.exit_status=0;
      break;
    }
    if (!in_string && (line[0] == '#' ||
		       (line[0] == '-' && line[1] == '-') ||
		       line[0] == 0))
      continue;					// Skip comment lines

    /*
      Check if line is a mysql command line
@@ -1260,15 +1261,21 @@ static bool add_line(String &buffer,char *line,char *in_string,

  for (pos=out=line ; (inchar= (uchar) *pos) ; pos++)
  {
    if (my_isspace(charset_info,inchar) && out == line && 
    if (!preserve_comments)
    {
      // Skip spaces at the beggining of a statement
      if (my_isspace(charset_info,inchar) && (out == line) &&
          buffer.is_empty())
        continue;
    }
        
#ifdef USE_MB
    // Accept multi-byte characters as-is
    int length;
    if (use_mb(charset_info) &&
        (length= my_ismbchar(charset_info, pos, end_of_line)))
    {
      if (!*ml_comment)
      if (!*ml_comment || preserve_comments)
      {
        while (length--)
          *out++ = *pos++;
@@ -1294,8 +1301,13 @@ static bool add_line(String &buffer,char *line,char *in_string,
      }
      if ((com=find_command(NullS,(char) inchar)))
      {
        const String tmp(line,(uint) (out-line), charset_info);
        buffer.append(tmp);
        // Flush previously accepted characters
        if (out != line)
        {
          buffer.append(line, (uint) (out-line));
          out= line;
        }
        
        if ((*com->func)(&buffer,pos-1) > 0)
          DBUG_RETURN(1);                       // Quit
        if (com->takes_params)
@@ -1323,7 +1335,6 @@ static bool add_line(String &buffer,char *line,char *in_string,
              pos+= delimiter_length - 1; // Point at last delim char
          }
        }
        out=line;
      }
      else
      {
@@ -1336,26 +1347,69 @@ static bool add_line(String &buffer,char *line,char *in_string,
      }
    }
    else if (!*ml_comment && !*in_string &&
             (*pos == *delimiter && is_prefix(pos + 1, delimiter + 1) ||
              buffer.length() == 0 && (out - line) >= 9 &&
              !my_strcasecmp(charset_info, line, "delimiter")))
             (out - line) >= 9 &&
             !my_strnncoll(charset_info, (uchar*) pos, 9,
                           (const uchar*) "delimiter", 9) &&
             my_isspace(charset_info, pos[9]))
    {
      uint old_delimiter_length= delimiter_length;
      // Flush previously accepted characters
      if (out != line)
	buffer.append(line, (uint) (out - line));	// Add this line
      if ((com= find_command(buffer.c_ptr(), 0)))
      {
        if (com->func == com_delimiter)
        buffer.append(line, (uint32) (out - line));
        out= line;
      }

      // Flush possible comments in the buffer
      if (!buffer.is_empty())
      {
        if (com_go(&buffer, 0) > 0) // < 0 is not fatal
          DBUG_RETURN(1);
        buffer.length(0);
      }

      /*
        Delimiter wants the get rest of the given line as argument to
        allow one to change ';' to ';;' and back
      */
          char *end= strend(pos);
          buffer.append(pos, (uint) (end - pos));
          /* Ensure pos will point at \0 after the pos+= below */
          pos= end - old_delimiter_length + 1;
      buffer.append(pos);
      if (com_delimiter(&buffer, pos) > 0)
        DBUG_RETURN(1);

      buffer.length(0);
      break;
    }
    else if (!*ml_comment && !*in_string && is_prefix(pos, delimiter))
    {
      // Found a statement. Continue parsing after the delimiter
      pos+= delimiter_length;

      if (preserve_comments)
      {
        while (my_isspace(charset_info, *pos))
          *out++= *pos++;
      }
      // Flush previously accepted characters
      if (out != line)
      {
        buffer.append(line, (uint32) (out-line));
        out= line;
      }

      if (preserve_comments && ((*pos == '#') ||
                                ((*pos == '-') &&
                                 (pos[1] == '-') &&
                                 my_isspace(charset_info, pos[2]))))
      {
        // Add trailing single line comments to this statement
        buffer.append(pos);
        pos+= strlen(pos);
      }

      pos--;

      if ((com= find_command(buffer.c_ptr(), 0)))
      {
          
        if ((*com->func)(&buffer, buffer.c_ptr()) > 0)
          DBUG_RETURN(1);                       // Quit 
      }
@@ -1365,16 +1419,33 @@ static bool add_line(String &buffer,char *line,char *in_string,
          DBUG_RETURN(1);
      }
      buffer.length(0);
      out= line;
      pos+= old_delimiter_length - 1;
    }
    else if (!*ml_comment && (!*in_string && (inchar == '#' ||
			      inchar == '-' && pos[1] == '-' &&
			      my_isspace(charset_info,pos[2]))))
      break;					// comment to end of line
    {
      // Flush previously accepted characters
      if (out != line)
      {
        buffer.append(line, (uint32) (out - line));
        out= line;
      }

      // comment to end of line
      if (preserve_comments)
        buffer.append(pos);

      break;
    }
    else if (!*in_string && inchar == '/' && *(pos+1) == '*' &&
	     *(pos+2) != '!')
    {
      if (preserve_comments)
      {
        *out++= *pos++;                       // copy '/'
        *out++= *pos;                         // copy '*'
      }
      else
        pos++;
      *ml_comment= 1;
      if (out != line)
@@ -1385,8 +1456,21 @@ static bool add_line(String &buffer,char *line,char *in_string,
    }
    else if (*ml_comment && !ss_comment && inchar == '*' && *(pos + 1) == '/')
    {
      if (preserve_comments)
      {
        *out++= *pos++;                       // copy '*'
        *out++= *pos;                         // copy '/'
      }
      else
        pos++;
      *ml_comment= 0;
      if (out != line)
      {
        buffer.append(line, (uint32) (out - line));
        out= line;
      }
      // Consumed a 2 chars or more, and will add 1 at most,
      // so using the 'line' buffer to edit data in place is ok.
      need_space= 1;
    }      
    else
@@ -1401,13 +1485,11 @@ static bool add_line(String &buffer,char *line,char *in_string,
      else if (!*ml_comment && !*in_string &&
	       (inchar == '\'' || inchar == '"' || inchar == '`'))
	*in_string= (char) inchar;
      if (!*ml_comment)
      if (!*ml_comment || preserve_comments)
      {
        if (need_space && !my_isspace(charset_info, (char)inchar))
        {
          *out++= ' ';
        need_space= 0;
        }
        *out++= (char) inchar;
      }
    }
@@ -1418,7 +1500,7 @@ static bool add_line(String &buffer,char *line,char *in_string,
    uint length=(uint) (out-line);
    if (buffer.length() + length >= buffer.alloced_length())
      buffer.realloc(buffer.length()+length+IO_SIZE);
    if (!(*ml_comment) && buffer.append(line,length))
    if ((!*ml_comment || preserve_comments) && buffer.append(line, length))
      DBUG_RETURN(1);
  }
  DBUG_RETURN(0);
+50 −0
Original line number Diff line number Diff line
drop table if exists t1;
drop function if exists foofct;
drop procedure if exists empty;
drop procedure if exists foosp;
drop procedure if exists nicesp;
drop trigger if exists t1_empty;
drop trigger if exists t1_bi;
"Pass 1 : --disable-comments"
1
1
2
2
foofct("call 1")
call 1
Function	sql_mode	Create Function
foofct		CREATE DEFINER=`root`@`localhost` FUNCTION `foofct`(x char(20)) RETURNS char(20) CHARSET latin1\nreturn\n\n\n\nx
foofct("call 2")
call 2
Function	sql_mode	Create Function
foofct		CREATE DEFINER=`root`@`localhost` FUNCTION `foofct`(x char(20)) RETURNS char(20) CHARSET latin1\nbegin\n  \n  \n  \n\n  \n\n  \n  return x;\nend
Procedure	sql_mode	Create Procedure
empty		CREATE DEFINER=`root`@`localhost` PROCEDURE `empty`()\nbegin\nend
id	data
foo	42
Procedure	sql_mode	Create Procedure
foosp		CREATE DEFINER=`root`@`localhost` PROCEDURE `foosp`()\ninsert into test.t1\n\n\n\n\n  \n\n  \n  values ("foo", 42)
Procedure	sql_mode	Create Procedure
nicesp		CREATE DEFINER=`root`@`localhost` PROCEDURE `nicesp`(a int)\nbegin\n  \n  declare b int;\n  declare c float;\n\n  \n  \n\n  \nend
"Pass 2 : --enable-comments"
1
1
2
2
foofct("call 1")
call 1
Function	sql_mode	Create Function
foofct		CREATE DEFINER=`root`@`localhost` FUNCTION `foofct`(x char(20)) RETURNS char(20) CHARSET latin1\nreturn\n-- comment 1a\n# comment 1b\n/* comment 1c */\nx # after body, on same line
foofct("call 2")
call 2
Function	sql_mode	Create Function
foofct		CREATE DEFINER=`root`@`localhost` FUNCTION `foofct`(x char(20)) RETURNS char(20) CHARSET latin1\nbegin\n  -- comment 1a\n  # comment 1b\n  /*\n     comment 1c\n  */\n\n  -- empty line below\n\n  -- empty line above\n  return x;\nend
Procedure	sql_mode	Create Procedure
empty		CREATE DEFINER=`root`@`localhost` PROCEDURE `empty`()\nbegin\nend
id	data
foo	42
Procedure	sql_mode	Create Procedure
foosp		CREATE DEFINER=`root`@`localhost` PROCEDURE `foosp`()\ninsert into test.t1\n## These comments are part of the procedure body, and should be kept.\n# Comment 2a\n-- Comment 2b\n/* Comment 2c */\n  -- empty line below\n\n  -- empty line above\n  values ("foo", 42) # comment 3, still part of the body
Procedure	sql_mode	Create Procedure
nicesp		CREATE DEFINER=`root`@`localhost` PROCEDURE `nicesp`(a int)\nbegin\n  -- declare some variables here\n  declare b int;\n  declare c float;\n\n  -- do more stuff here\n  -- commented nicely and so on\n\n  -- famous last words ...\nend
End of 5.0 tests
+177 −0
Original line number Diff line number Diff line
##============================================================================
## Notes
##============================================================================

# Test case for Bug#11230

# The point of this test is to make sure that '#', '-- ' and '/* ... */'
# comments, as well as empty lines, are sent from the client to the server.
# This is to ensure better error reporting, and to keep comments in the code
# for stored procedures / functions / triggers (Bug#11230).
# As a result, be careful when editing comments in this script, they do
# matter.
#
# Also, note that this is a script for **mysql**, not mysqltest.
# This is critical, as the mysqltest client interprets comments differently.

##============================================================================
## Setup
##============================================================================

## See mysql_comments.test for initial cleanup

# Test tables
#
# t1 is reused throughout the file, and dropped at the end.
#
drop table if exists t1;
create table t1 (
  id   char(16) not null default '',
  data int not null
);

##============================================================================
## Comments outside statements
##============================================================================

# Ignored 1a
-- Ignored 1b
/*
   Ignored 1c
*/

select 1;

##============================================================================
## Comments inside statements
##============================================================================

select # comment 1a
# comment 2a
-- comment 2b
/*
   comment 2c
*/
2
; # not strictly inside, but on same line
# ignored

##============================================================================
## Comments inside functions
##============================================================================

drop function if exists foofct ;

create function foofct (x char(20))
returns char(20)
/* not inside the body yet */
return
-- comment 1a
# comment 1b
/* comment 1c */
x; # after body, on same line

select foofct("call 1");

show create function foofct;
drop function foofct;

delimiter |

create function foofct(x char(20))
returns char(20)
begin
  -- comment 1a
  # comment 1b
  /*
     comment 1c
  */

  -- empty line below

  -- empty line above
  return x;
end|

delimiter ;

select foofct("call 2");

show create function foofct;
drop function foofct;

##============================================================================
## Comments inside stored procedures
##============================================================================

# Empty statement
drop procedure if exists empty;
create procedure empty()
begin
end;

call empty();
show create procedure empty;
drop procedure empty;

drop procedure if exists foosp;

## These comments are before the create, and will be lost
# Comment 1a
-- Comment 1b
/*
   Comment 1c
 */
create procedure foosp()
/* Comment not quiet in the body yet */
  insert into test.t1
## These comments are part of the procedure body, and should be kept.
# Comment 2a
-- Comment 2b
/* Comment 2c */
  -- empty line below

  -- empty line above
  values ("foo", 42); # comment 3, still part of the body
## After the ';', therefore not part of the body
# comment 4a
-- Comment 4b
/*
   Comment 4c
 */

call foosp();
select * from t1;
delete from t1;
show create procedure foosp;
drop procedure foosp;

drop procedure if exists nicesp;

delimiter |

create procedure nicesp(a int)
begin
  -- declare some variables here
  declare b int;
  declare c float;

  -- do more stuff here
  -- commented nicely and so on

  -- famous last words ...
end|

delimiter ;

show create procedure nicesp;
drop procedure nicesp;

# Triggers can be tested only in 5.1, since 5.0 does not have 
# SHOW CREATE TRIGGER

##============================================================================
## Cleanup
##============================================================================

drop table t1;
+37 −0
Original line number Diff line number Diff line
# This test should work in embedded server after we fix mysqltest
-- source include/not_embedded.inc
###################### mysql_comments.test #############################
#                                                                      #
# Testing of comments handling by the command line client (mysql)      #
#                                                                      #
# Creation:                                                            #
# 2007-10-29 akopytov Implemented this test as a part of fixes for     #
#                     bug #26215 and bug #11230                        #
#                                                                      #
########################################################################

#
# Bug #11230: Keeping comments when storing stored procedures
#

# See the content of mysql_comments.sql
# Set the test database to a known state before running the tests.
--disable_warnings
drop table if exists t1;
drop function if exists foofct;
drop procedure if exists empty;
drop procedure if exists foosp;
drop procedure if exists nicesp;
drop trigger if exists t1_empty;
drop trigger if exists t1_bi;
--enable_warnings

# Test without comments
--echo "Pass 1 : --disable-comments"
--exec $MYSQL --disable-comments test 2>&1 < "./t/mysql_comments.sql"

# Test with comments
--echo "Pass 2 : --enable-comments"
--exec $MYSQL --enable-comments test 2>&1 < "./t/mysql_comments.sql"

--echo End of 5.0 tests