Commit 0a53b531 authored by unknown's avatar unknown
Browse files

WL# 2094 Federated Storage Handler, added fixes per suggestions by Bar and Antony


mysql-test/r/federated.result:
  new test results
mysql-test/t/federated.test:
  added order by, group by
sql/ha_federated.cc:
  - added 'scheme' to URL
  - added proper escaping
  - made sure &my_charset_bin is being used throughout handler
  - made sure create_table catches improper URL in comment upon table creation
sql/ha_federated.h:
  added scheme to share
parent 286bd2ad
Loading
Loading
Loading
Loading
+59 −10

File changed.

Preview size limit exceeded, changes collapsed.

+43 −7
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ drop database if exists federated;
--enable_warnings
create database federated;

CREATE TABLE federated.t1 ( `id` int(20) NOT NULL auto_increment, `name` varchar(32) NOT NULL default '', `other` int(20) NOT NULL default '0', created datetime default '2004-04-04 04:04:04', PRIMARY KEY  (`id`), KEY `name` (`name`), KEY `other_key` (`other`)) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='root@127.0.0.1:9308/federated/t1';
CREATE TABLE federated.t1 ( `id` int(20) NOT NULL auto_increment, `name` varchar(32) NOT NULL default '', `other` int(20) NOT NULL default '0', created datetime default '2004-04-04 04:04:04', PRIMARY KEY  (`id`), KEY `name` (`name`), KEY `other_key` (`other`)) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='mysql://root@127.0.0.1:9308/federated/t1';

insert into federated.t1 (name, other) values ('First Name', 11111);
insert into federated.t1 (name, other) values ('Second Name', 22222);
@@ -55,6 +55,10 @@ update federated.t1 set name = 'Third name' where name = '3rd name';
select * from federated.t1 where name = 'Third name';
# rnd_post, ::position
select * from federated.t1 order by id DESC;
select * from federated.t1 order by name;
select * from federated.t1 order by name DESC;
select * from federated.t1 order by name ASC;
select * from federated.t1 group by other;

# ::delete_row
delete from federated.t1 where id = 5; 
@@ -65,7 +69,7 @@ delete from federated.t1;
select * from federated.t1 where id = 5;

drop table if exists federated.t1;
CREATE TABLE federated.t1 ( `id` int(20) NOT NULL auto_increment, `name` varchar(32), `other` varchar(20), PRIMARY KEY  (`id`) ) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='root@127.0.0.1:9308/federated/t1';
CREATE TABLE federated.t1 ( `id` int(20) NOT NULL auto_increment, `name` varchar(32), `other` varchar(20), PRIMARY KEY  (`id`) ) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='mysql://root@127.0.0.1:9308/federated/t1';

connection slave;
drop table if exists federated.t1;
@@ -100,7 +104,7 @@ CREATE TABLE federated.t1 (id int, name varchar(32), floatval float, other int)
connection master;
# test NULLs
drop table if exists federated.t1;
CREATE TABLE federated.t1 (id int, name varchar(32), floatval float, other int) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='root@127.0.0.1:9308/federated/t1';
CREATE TABLE federated.t1 (id int, name varchar(32), floatval float, other int) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='mysql://root@127.0.0.1:9308/federated/t1';
# these both should be the same
insert into federated.t1 values (NULL, NULL, NULL, NULL);
insert into federated.t1 values ();
@@ -116,7 +120,7 @@ CREATE TABLE federated.t1 ( blurb_id int NOT NULL DEFAULT 0, blurb text default

connection master;
drop table if exists federated.t1;
CREATE TABLE federated.t1 ( blurb_id int NOT NULL DEFAULT 0, blurb text default '', primary key(blurb_id)) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='root@127.0.0.1:9308/federated/t1'; 
CREATE TABLE federated.t1 ( blurb_id int NOT NULL DEFAULT 0, blurb text default '', primary key(blurb_id)) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='mysql://root@127.0.0.1:9308/federated/t1'; 

INSERT INTO federated.t1 VALUES (1, " MySQL supports a number of column types in several categories: numeric types, date and time types, and string (character) types.  This chapter first gives an overview of these column types, and then provides a more detailed description of the properties of the types in each category, and a summary of the column type storage requirements.  The overview is intentionally brief.  The more detailed descriptions should be consulted for additional information about particular column types, such as the allowable formats in which you can specify values.");
INSERT INTO federated.t1 VALUES (2, "All arithmetic is done using signed BIGINT or DOUBLE values, so you should not use unsigned big integers larger than 9223372036854775807 (63 bits) except with bit functions! If you do that, some of the last digits in the result may be wrong because of rounding errors when converting a BIGINT value to a DOUBLE.");
@@ -130,7 +134,7 @@ create table federated.t1 (a int not null, b int not null, c int not null, prima

connection master;
drop table if exists federated.t1;
create table federated.t1 (a int not null, b int not null, c int not null, primary key (a),key(b)) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='root@127.0.0.1:9308/federated/t1';
create table federated.t1 (a int not null, b int not null, c int not null, primary key (a),key(b)) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='mysql://root@127.0.0.1:9308/federated/t1';

insert into federated.t1 values (3,3,3),(1,1,1),(2,2,2),(4,4,4);
explain select * from federated.t1 order by a;
@@ -396,7 +400,7 @@ int, i967 int, i968 int, i969 int, i970 int, i971 int, i972 int, i973 int, i974
int, i975 int, i976 int, i977 int, i978 int, i979 int, i980 int, i981 int, i982
int, i983 int, i984 int, i985 int, i986 int, i987 int, i988 int, i989 int, i990
int, i991 int, i992 int, i993 int, i994 int, i995 int, i996 int, i997 int, i998
int, i999 int, i1000 int, b blob) row_format=dynamic ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='root@127.0.0.1:9308/federated/t1';
int, i999 int, i1000 int, b blob) row_format=dynamic ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='mysql://root@127.0.0.1:9308/federated/t1';
insert into federated.t1 values (1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
@@ -452,12 +456,44 @@ create table federated.t1 (id int NOT NULL auto_increment, code char(20) NOT NUL

connection master;
drop table if exists federated.t1;
create table federated.t1 (id int NOT NULL auto_increment, code char(20) NOT NULL, fileguts blob, creation_date datetime, entered_time datetime default '2004-04-04 04:04:04', primary key(id), index(code)) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='root@127.0.0.1:9308/federated/t1'; 
create table federated.t1 (id int NOT NULL auto_increment, code char(20) NOT NULL, fileguts blob, creation_date datetime, entered_time datetime default '2004-04-04 04:04:04', primary key(id), index(code)) ENGINE="FEDERATED" DEFAULT CHARSET=latin1 COMMENT='mysql://root@127.0.0.1:9308/federated/t1'; 
insert into federated.t1 (code, fileguts, creation_date) values ('ASDFWERQWETWETAWETA', '*()w*09*$()*#)(*09*^90*d)(*s()d8g)(s*ned)(*)(s*d)(*hn(d*)(*sbn)D((#$*(#*%%&#&^$#&#&#&#&^&#*&*#$*&^*(&#(&Q*&&(*!&!(*&*(#&*(%&#<S-F8>*<S-F8><S-F8><S-F8>#<S-F8>#<S-F8>#<S-F8>[[', '2003-03-03 03:03:03');
insert into federated.t1 (code, fileguts, creation_date) values ('DEUEUEUEUEUEUEUEUEU', '*()w*09*$()*#)(*09*^90*d)(*s()d8g)(s*ned)(*)(s*d)(*hn(d*)(*sbn)D((#$*(#*%%&#&^$#&#&#&#&^&#*&*#$*&^*(&#(&Q*&&(*!&!(*&*(#&*(%&#<S-F8>*<S-F8><S-F8><S-F8>#<S-F8>#<S-F8>#<S-F8>[[', '2004-04-04 04:04:04');
select * from federated.t1;
drop table if exists federated.t1;

# TODO
# 
# create table federated.t1 (a char(20)) charset=cp1251 ENGINE="FEDERATED" COMMENT="mysql://root@127.0.0.1:9308/federated/t1";
# 
# connection slave;
# drop table if exists federated.t1;
# create table federated.t1 (a char(20)) charset=cp1251;
# 
# connection master;
# insert into federated.t1 values (_cp1251'À-ÁÂÃ-1');
# insert into federated.t1 values (_cp1251'Á-ÂÃÄ-2');
# set names cp1251;
# insert into federated.t1 values ('Â-ÃÄÅ-3');
# insert into federated.t1 values ('Ã-ŨÆ-4');
# select * from federated.t1;
# select hex(a) from federated.t1;
# select hex(a) from federated.t1 order by a desc;
# update federated.t1 set a='À-ÁÂÃ-1íîâûé' where a='À-ÁÂÃ-1';
# select * from federated.t1;
# delete from federated.t1 where a='Ã-ŨÆ-4';
# select * from federated.t1;
# delete from federated.t1 where a>'Â-';
# select * from federated.t1;
# set names default;
# 
# drop table if exists federated.t1;
# 

connection slave;
drop table if exists federated.t1;

connection master;
--disable_warnings
drop database if exists federated;
--enable_warnings
+174 −107
Original line number Diff line number Diff line
@@ -43,13 +43,23 @@
  The create table will simply create the .frm file, and within the 
  "CREATE TABLE" SQL, there SHALL be any of the following : 

  comment=username:password@hostname:port/database/tablename
  comment=username@hostname/database/tablename
  comment=username:password@hostname/database/tablename
  comment=username:password@hostname/database/tablename
  comment=scheme://username:password@hostname:port/database/tablename
  comment=scheme://username@hostname/database/tablename
  comment=scheme://username:password@hostname/database/tablename
  comment=scheme://username:password@hostname/database/tablename

  An example would be:

  comment=mysql://username:password@hostname:port/database/tablename

  ***IMPORTANT***

  Only 'mysql://' is supported at this release.


  This comment connection string is necessary for the handler to be 
  able to connect to the remote server.

  This string is necessary for the handler to be able to connect to the remote
  server.

  The basic flow is this:

@@ -62,22 +72,27 @@

  What this handler does and doesn't support
  ------------------------------------------
  * There will not be support for transactions
  * Tables MUST be created on the remote server prior to any action on those
    tables via the handler, first version. IMPORTANT: IF you MUST use the 
    federated storage engine type on the REMOTE end, MAKE SURE [ :) ] That
    the table you connect to IS NOT a table pointing BACK to your ORIGNAL 
    table! You know  and have heard the screaching of audio feedback? You
    know putting two mirror in front of each other how the reflection 
    continues for eternity? Well, need I say more?!
  * There will not be support for transactions.
  * There is no way for the handler to know if the database on the remote end
    has changed. The reason for this is that this database has to work like a
    data file that would never be written to by anything other than the 
    database. The integrity of the data in the local table could be breached 
    if there was any change to the remote database.
  * Support for SELECT, INSERT, UPDATE , DELETE, indexes
  * Support for SELECT, INSERT, UPDATE , DELETE, indexes.
  * No ALTER TABLE, DROP TABLE or any other Data Definition Language calls.
  * Prepared statements will not be used in the first implementation, it 
    remains to to be seen whether the limited subset of the client API for the
    server supports this
    server supports this.
  * This uses SELECT, INSERT, UPDATE, DELETE and not HANDLER for its 
    implementation 
  * Tables must be created on the remote server prior to any action on those
    tables via the handler, first version.
  * This will not work with the query cache
    implementation. 
  * This will not work with the query cache.

   Method calls

@@ -86,7 +101,6 @@
   (SELECT)

   "SELECT * FROM foo" 
    ha_federated::external_lock
    ha_federated::info
    ha_federated::scan_time:
    ha_federated::rnd_init: share->select_query SELECT * FROM foo
@@ -100,14 +114,12 @@

    ha_federated::rnd_end
    ha_federated::extra
    ha_federated::external_lock
    ha_federated::reset

    (INSERT)

    "INSERT INTO foo (id, ts) VALUES (2, now());"

    ha_federated::external_lock
    ha_federated::write_row

    <for every field/column>
@@ -115,7 +127,6 @@
    ha_federated::quote_data
    </for every field/column>

    ha_federated::external_lock
    ha_federated::reset

    (UPDATE)
@@ -374,10 +385,17 @@ static byte* federated_get_key(FEDERATED_SHARE *share,uint *length,

    This string MUST be in the format of any of these:

    username:password@hostname:port/database/table
    username@hostname/database/table
    username@hostname:port/database/table
    username:password@hostname/database/table
scheme://username:password@hostname:port/database/table
scheme://username@hostname/database/table
scheme://username@hostname:port/database/table
scheme://username:password@hostname/database/table

  An Example:

  mysql://joe:joespass@192.168.1.111:9308/federated/testtable

  ***IMPORTANT***
  Currently, only "mysql://" is supported.

    'password' and 'port' are both optional.

@@ -386,11 +404,31 @@ static byte* federated_get_key(FEDERATED_SHARE *share,uint *length,
    -1  failure, wrong string format    

*/
int parse_url(FEDERATED_SHARE *share, TABLE *table)
int parse_url(FEDERATED_SHARE *share, TABLE *table, uint table_create_flag)
{
  DBUG_ENTER("ha_federated::parse_url");

  share->username= my_strdup(table->comment, MYF(0));
  // This either get set or will remain the same.
  share->port= 0;
  uint error_num= table_create_flag ? ER_CANT_CREATE_TABLE : ER_CONNECT_TO_MASTER ;

  share->scheme= my_strdup(table->comment, MYF(0));


  if (share->username= strstr(share->scheme, "://"))
  {
    share->scheme[share->username - share->scheme] = '\0';
    if (strcmp(share->scheme, "mysql") != 0)
    {
      DBUG_PRINT("ha_federated::parse_url",
                 ("The federated handler currently only supports connecting\
                  to a MySQL database!!!\n"));
      my_error(error_num, MYF(0),
        "ERROR: federated handler only supports remote 'mysql://' database");
      DBUG_RETURN(-1);
    }
    share->username+= 3;

    if (share->hostname= strchr(share->username, '@')) 
    {
      share->username[share->hostname - share->username]= '\0';
@@ -406,6 +444,8 @@ int parse_url(FEDERATED_SHARE *share, TABLE *table)
        {
          DBUG_PRINT("ha_federated::parse_url",
                     ("this connection string is not in the correct format!!!\n"));
          my_error(error_num, MYF(0),
                     "this connection string is not in the correct format!!!\n");
          DBUG_RETURN(-1);
        }
        /* 
@@ -417,15 +457,15 @@ int parse_url(FEDERATED_SHARE *share, TABLE *table)
          share->password= NULL;
      }
      else
    {
        share->username= share->username;
      share->password= NULL;
    }

      // make sure there isn't an extra / or @
      if (strchr(share->username, '/') || strchr(share->hostname, '@'))
      {
        DBUG_PRINT("ha_federated::parse_url",
                   ("this connection string is not in the correct format!!!\n"));
        my_error(error_num, MYF(0),
          "this connection string is not in the correct format!!!\n");
        DBUG_RETURN(-1);
      }

@@ -438,13 +478,10 @@ int parse_url(FEDERATED_SHARE *share, TABLE *table)
        {
          share->hostname[share->sport - share->hostname]= '\0';
          share->sport++;
        share->port= atoi(share->sport);

      }
      else
      {
          if (share->sport[0] == '\0')
            share->sport= NULL;
        share->port= NULL;
          else
            share->port= atoi(share->sport);
        }

        if (share->table_base_name= strchr(share->database, '/'))
@@ -456,6 +493,8 @@ int parse_url(FEDERATED_SHARE *share, TABLE *table)
        {
          DBUG_PRINT("ha_federated::parse_url",
                     ("this connection string is not in the correct format!!!\n"));
          my_error(error_num, MYF(0),
            "this connection string is not in the correct format!!!\n");
          DBUG_RETURN(-1);
        }
      }
@@ -463,6 +502,8 @@ int parse_url(FEDERATED_SHARE *share, TABLE *table)
      {
        DBUG_PRINT("ha_federated::parse_url",
                   ("this connection string is not in the correct format!!!\n"));
        my_error(error_num, MYF(0),
          "this connection string is not in the correct format!!!\n");
        DBUG_RETURN(-1);
      }
      // make sure there's not an extra /
@@ -470,17 +511,34 @@ int parse_url(FEDERATED_SHARE *share, TABLE *table)
      {
        DBUG_PRINT("ha_federated::parse_url",
                   ("this connection string is not in the correct format!!!\n"));
        my_error(error_num, MYF(0),
          "this connection string is not in the correct format!!!\n");
        DBUG_RETURN(-1);
      }
      if (share->hostname[0] == '\0')
        share->hostname= NULL;

      DBUG_PRINT("ha_federated::parse_url", 
      ("username %s password %s hostname %s port %d database %s tablename %s\n",
      share->username, share->password, share->hostname,
        ("scheme %s username %s password %s \
         hostname %s port %d database %s tablename %s\n",
         share->scheme, share->username, share->password, share->hostname,
         share->port, share->database, share->table_base_name));
    }
    else
    {
      DBUG_PRINT("ha_federated::parse_url",
        ("this connection string is not in the correct format!!!\n"));
      my_error(error_num, MYF(0),
        "this connection string is not in the correct format!!!\n");
      DBUG_RETURN(-1);
    }
  }
  else
  { 
    DBUG_PRINT("ha_federated::parse_url",
      ("this connection string is not in the correct format!!!\n"));
    my_error(error_num, MYF(0),
      "this connection string is not in the correct format!!!\n");
    DBUG_RETURN(-1);
  }
  DBUG_RETURN(0);
@@ -522,7 +580,7 @@ uint ha_federated::convert_row_to_internal_format(byte *record, MYSQL_ROW row)
        changed system_charset_info to default_charset_info because
        testing revealed that german text was not being retrieved properly
      */
      (*field)->store(row[x], strlen(row[x]), default_charset_info);
      (*field)->store(row[x], strlen(row[x]), &my_charset_bin);
  }

  DBUG_RETURN(0);
@@ -547,14 +605,20 @@ uint ha_federated::convert_row_to_internal_format(byte *record, MYSQL_ROW row)
*/
void ha_federated::quote_data(String *unquoted_string, Field *field )
{
  char quoted_string_buffer[IO_SIZE];
  String quoted_string(quoted_string_buffer, 
                       sizeof(quoted_string_buffer), &my_charset_bin);
  quoted_string.length(0);
  char escaped_string[IO_SIZE];
  char *unquoted_string_buffer;
  
  unquoted_string_buffer= unquoted_string->c_ptr_quick();

  int quote_flag;
  DBUG_ENTER("ha_federated::quote_data");
  DBUG_PRINT("ha_federated::quote_data", ("unquoted string %s", unquoted_string->c_ptr_quick()));
  // this is the same call that mysql_real_escape_string() calls
  escape_string_for_mysql(&my_charset_bin, (char *)escaped_string, 
    unquoted_string->c_ptr_quick(), unquoted_string->length());

  DBUG_PRINT("ha_federated::quote_data",
    ("escape_string_for_mysql unescaped %s escaped %s",
     unquoted_string->c_ptr_quick(), escaped_string));

  if (field->is_null())
  {
@@ -575,14 +639,15 @@ void ha_federated::quote_data(String *unquoted_string, Field *field )
  }
  else
  {
    quoted_string.append("'");
    quoted_string.append(*unquoted_string);
    quoted_string.append("'");
    unquoted_string->copy(quoted_string);
    // reset string, then re-append with quotes and escaped values
    unquoted_string->length(0);
    unquoted_string->append("'");
    unquoted_string->append((char *)escaped_string);
    unquoted_string->append("'");
  }
  DBUG_PRINT("ha_federated::quote_data",
             ("quote_flag %d unquoted_string %s quoted_string %s", 
              quote_flag, unquoted_string->c_ptr_quick(), quoted_string.c_ptr_quick()));
    ("FINAL quote_flag %d unquoted_string %s escaped_string %s", 
    quote_flag, unquoted_string->c_ptr_quick(), escaped_string));
  DBUG_VOID_RETURN;
}

@@ -648,7 +713,7 @@ int load_conn_info(FEDERATED_SHARE *share, TABLE *table)
  DBUG_ENTER("ha_federated::load_conn_info");
  int retcode;
 
  retcode= parse_url(share, table);
  retcode= parse_url(share, table, 0);

  if (retcode < 0)
  {
@@ -1221,7 +1286,6 @@ int ha_federated::update_row(
int ha_federated::delete_row(const byte * buf)
{
  int x= 0;
  int quote_flag;
  char delete_buffer[IO_SIZE];
  char data_buffer[IO_SIZE];

@@ -1244,23 +1308,16 @@ int ha_federated::delete_row(const byte * buf)
    {
      delete_string.append(" IS ");
      data_string.append("NULL");
      quote_flag= 0;
    }
    else
    {
      delete_string.append("=");
      (*field)->val_str(&data_string);
      quote_flag= type_quote((*field)->type());
      quote_data(&data_string, *field);
    }
  
    if (quote_flag)
      delete_string.append('\'');

    delete_string.append(data_string);
    data_string.copy(NULL);

    if (quote_flag)
      delete_string.append('\'');
    data_string.length(0);

    if (x+1 < table->fields)
      delete_string.append(" AND ");
@@ -1650,6 +1707,16 @@ int ha_federated::create(const char *name, TABLE *table_arg,
                       HA_CREATE_INFO *create_info)
{
  DBUG_ENTER("ha_federated::create");
  int retcode;
  FEDERATED_SHARE *tmp;
  retcode= parse_url(tmp, table_arg, 1);
  if (retcode < 0)
  {
    DBUG_PRINT("ha_federated::create",
      ("ERROR: on table creation for %s called parse_url, retcode %d",
       create_info->data_file_name, retcode));
    DBUG_RETURN(ER_CANT_CREATE_TABLE);
  }
  DBUG_RETURN(0);
}
#endif /* HAVE_FEDERATED_DB */
+1 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ typedef struct st_federated_share {
  // the primary select query to be used in rnd_init
  char *select_query;
  // remote host info, parse_url supplies
  char *scheme;
  char *hostname;
  char *username;
  char *password;