summaryrefslogtreecommitdiff
path: root/sql/ha_federated.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/ha_federated.cc')
-rw-r--r--sql/ha_federated.cc2961
1 files changed, 0 insertions, 2961 deletions
diff --git a/sql/ha_federated.cc b/sql/ha_federated.cc
deleted file mode 100644
index 6944dc5a030..00000000000
--- a/sql/ha_federated.cc
+++ /dev/null
@@ -1,2961 +0,0 @@
-/* Copyright (C) 2004 MySQL AB
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; version 2 of the License.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
-
-/*
-
- MySQL Federated Storage Engine
-
- ha_federated.cc - MySQL Federated Storage Engine
- Patrick Galbraith and Brian Aker, 2004
-
- This is a handler which uses a foreign database as the data file, as
- opposed to a handler like MyISAM, which uses .MYD files locally.
-
- How this handler works
- ----------------------------------
- Normal database files are local and as such: You create a table called
- 'users', a file such as 'users.MYD' is created. A handler reads, inserts,
- deletes, updates data in this file. The data is stored in particular format,
- so to read, that data has to be parsed into fields, to write, fields have to
- be stored in this format to write to this data file.
-
- With MySQL Federated storage engine, there will be no local files
- for each table's data (such as .MYD). A foreign database will store
- the data that would normally be in this file. This will necessitate
- the use of MySQL client API to read, delete, update, insert this
- data. The data will have to be retrieve via an SQL call "SELECT *
- FROM users". Then, to read this data, it will have to be retrieved
- via mysql_fetch_row one row at a time, then converted from the
- column in this select into the format that the handler expects.
-
- The create table will simply create the .frm file, and within the
- "CREATE TABLE" SQL, there SHALL be any of the following :
-
- 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***
-
- This is a first release, conceptual release
- Only 'mysql://' is supported at this release.
-
-
- This comment connection string is necessary for the handler to be
- able to connect to the foreign server.
-
-
- The basic flow is this:
-
- SQL calls issues locally ->
- mysql handler API (data in handler format) ->
- mysql client API (data converted to SQL calls) ->
- foreign database -> mysql client API ->
- convert result sets (if any) to handler format ->
- handler API -> results or rows affected to local
-
- What this handler does and doesn't support
- ------------------------------------------
- * Tables MUST be created on the foreign 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 foreign database or table
- 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 foreign database.
- * 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.
- * This uses SELECT, INSERT, UPDATE, DELETE and not HANDLER for its
- implementation.
- * This will not work with the query cache.
-
- Method calls
-
- A two column table, with one record:
-
- (SELECT)
-
- "SELECT * FROM foo"
- ha_federated::info
- ha_federated::scan_time:
- ha_federated::rnd_init: share->select_query SELECT * FROM foo
- ha_federated::extra
-
- <for every row of data retrieved>
- ha_federated::rnd_next
- ha_federated::convert_row_to_internal_format
- ha_federated::rnd_next
- </for every row of data retrieved>
-
- ha_federated::rnd_end
- ha_federated::extra
- ha_federated::reset
-
- (INSERT)
-
- "INSERT INTO foo (id, ts) VALUES (2, now());"
-
- ha_federated::write_row
-
- ha_federated::reset
-
- (UPDATE)
-
- "UPDATE foo SET ts = now() WHERE id = 1;"
-
- ha_federated::index_init
- ha_federated::index_read
- ha_federated::index_read_idx
- ha_federated::rnd_next
- ha_federated::convert_row_to_internal_format
- ha_federated::update_row
-
- ha_federated::extra
- ha_federated::extra
- ha_federated::extra
- ha_federated::external_lock
- ha_federated::reset
-
-
- How do I use this handler?
- --------------------------
- First of all, you need to build this storage engine:
-
- ./configure --with-federated-storage-engine
- make
-
- Next, to use this handler, it's very simple. You must
- have two databases running, either both on the same host, or
- on different hosts.
-
- One the server that will be connecting to the foreign
- host (client), you create your table as such:
-
- CREATE TABLE test_table (
- id int(20) NOT NULL auto_increment,
- name varchar(32) NOT NULL default '',
- other int(20) NOT NULL default '0',
- PRIMARY KEY (id),
- KEY name (name),
- KEY other_key (other))
- ENGINE="FEDERATED"
- DEFAULT CHARSET=latin1
- COMMENT='root@127.0.0.1:9306/federated/test_federated';
-
- Notice the "COMMENT" and "ENGINE" field? This is where you
- respectively set the engine type, "FEDERATED" and foreign
- host information, this being the database your 'client' database
- will connect to and use as the "data file". Obviously, the foreign
- database is running on port 9306, so you want to start up your other
- database so that it is indeed on port 9306, and your federated
- database on a port other than that. In my setup, I use port 5554
- for federated, and port 5555 for the foreign database.
-
- Then, on the foreign database:
-
- CREATE TABLE test_table (
- id int(20) NOT NULL auto_increment,
- name varchar(32) NOT NULL default '',
- other int(20) NOT NULL default '0',
- PRIMARY KEY (id),
- KEY name (name),
- KEY other_key (other))
- ENGINE="<NAME>" <-- whatever you want, or not specify
- DEFAULT CHARSET=latin1 ;
-
- This table is exactly the same (and must be exactly the same),
- except that it is not using the federated handler and does
- not need the URL.
-
-
- How to see the handler in action
- --------------------------------
-
- When developing this handler, I compiled the federated database with
- debugging:
-
- ./configure --with-federated-storage-engine
- --prefix=/home/mysql/mysql-build/federated/ --with-debug
-
- Once compiled, I did a 'make install' (not for the purpose of installing
- the binary, but to install all the files the binary expects to see in the
- diretory I specified in the build with --prefix,
- "/home/mysql/mysql-build/federated".
-
- Then, I started the foreign server:
-
- /usr/local/mysql/bin/mysqld_safe
- --user=mysql --log=/tmp/mysqld.5555.log -P 5555
-
- Then, I went back to the directory containing the newly compiled mysqld,
- <builddir>/sql/, started up gdb:
-
- gdb ./mysqld
-
- Then, withn the (gdb) prompt:
- (gdb) run --gdb --port=5554 --socket=/tmp/mysqld.5554 --skip-innodb --debug
-
- Next, I open several windows for each:
-
- 1. Tail the debug trace: tail -f /tmp/mysqld.trace|grep ha_fed
- 2. Tail the SQL calls to the foreign database: tail -f /tmp/mysqld.5555.log
- 3. A window with a client open to the federated server on port 5554
- 4. A window with a client open to the federated server on port 5555
-
- I would create a table on the client to the foreign server on port
- 5555, and then to the federated server on port 5554. At this point,
- I would run whatever queries I wanted to on the federated server,
- just always remembering that whatever changes I wanted to make on
- the table, or if I created new tables, that I would have to do that
- on the foreign server.
-
- Another thing to look for is 'show variables' to show you that you have
- support for federated handler support:
-
- show variables like '%federat%'
-
- and:
-
- show storage engines;
-
- Both should display the federated storage handler.
-
-
- Testing
- -------
-
- There is a test for MySQL Federated Storage Handler in ./mysql-test/t,
- federatedd.test It starts both a slave and master database using
- the same setup that the replication tests use, with the exception that
- it turns off replication, and sets replication to ignore the test tables.
- After ensuring that you actually do have support for the federated storage
- handler, numerous queries/inserts/updates/deletes are run, many derived
- from the MyISAM tests, plus som other tests which were meant to reveal
- any issues that would be most likely to affect this handler. All tests
- should work! ;)
-
- To run these tests, go into ./mysql-test (based in the directory you
- built the server in)
-
- ./mysql-test-run federatedd
-
- To run the test, or if you want to run the test and have debug info:
-
- ./mysql-test-run --debug federated
-
- This will run the test in debug mode, and you can view the trace and
- log files in the ./mysql-test/var/log directory
-
- ls -l mysql-test/var/log/
- -rw-r--r-- 1 patg patg 17 4 Dec 12:27 current_test
- -rw-r--r-- 1 patg patg 692 4 Dec 12:52 manager.log
- -rw-rw---- 1 patg patg 21246 4 Dec 12:51 master-bin.000001
- -rw-rw---- 1 patg patg 68 4 Dec 12:28 master-bin.index
- -rw-r--r-- 1 patg patg 1620 4 Dec 12:51 master.err
- -rw-rw---- 1 patg patg 23179 4 Dec 12:51 master.log
- -rw-rw---- 1 patg patg 16696550 4 Dec 12:51 master.trace
- -rw-r--r-- 1 patg patg 0 4 Dec 12:28 mysqltest-time
- -rw-r--r-- 1 patg patg 2024051 4 Dec 12:51 mysqltest.trace
- -rw-rw---- 1 patg patg 94992 4 Dec 12:51 slave-bin.000001
- -rw-rw---- 1 patg patg 67 4 Dec 12:28 slave-bin.index
- -rw-rw---- 1 patg patg 249 4 Dec 12:52 slave-relay-bin.000003
- -rw-rw---- 1 patg patg 73 4 Dec 12:28 slave-relay-bin.index
- -rw-r--r-- 1 patg patg 1349 4 Dec 12:51 slave.err
- -rw-rw---- 1 patg patg 96206 4 Dec 12:52 slave.log
- -rw-rw---- 1 patg patg 15706355 4 Dec 12:51 slave.trace
- -rw-r--r-- 1 patg patg 0 4 Dec 12:51 warnings
-
- Of course, again, you can tail the trace log:
-
- tail -f mysql-test/var/log/master.trace |grep ha_fed
-
- As well as the slave query log:
-
- tail -f mysql-test/var/log/slave.log
-
- Files that comprise the test suit
- ---------------------------------
- mysql-test/t/federated.test
- mysql-test/r/federated.result
- mysql-test/r/have_federated_db.require
- mysql-test/include/have_federated_db.inc
-
-
- Other tidbits
- -------------
-
- These were the files that were modified or created for this
- Federated handler to work:
-
- ./configure.in
- ./sql/Makefile.am
- ./config/ac_macros/ha_federated.m4
- ./sql/handler.cc
- ./sql/mysqld.cc
- ./sql/set_var.cc
- ./sql/field.h
- ./sql/sql_string.h
- ./mysql-test/mysql-test-run(.sh)
- ./mysql-test/t/federated.test
- ./mysql-test/r/federated.result
- ./mysql-test/r/have_federated_db.require
- ./mysql-test/include/have_federated_db.inc
- ./sql/ha_federated.cc
- ./sql/ha_federated.h
-
-*/
-
-
-#include "mysql_priv.h"
-#ifdef USE_PRAGMA_IMPLEMENTATION
-#pragma implementation // gcc: Class implementation
-#endif
-
-#ifdef HAVE_FEDERATED_DB
-#include "ha_federated.h"
-
-#include "m_string.h"
-/* Variables for federated share methods */
-static HASH federated_open_tables; // Hash used to track open
- // tables
-pthread_mutex_t federated_mutex; // This is the mutex we use to
- // init the hash
-static int federated_init= FALSE; // Variable for checking the
- // init state of hash
-static char ident_quote_char= '`'; // Character for quoting
- // identifiers
-static char value_quote_char= '\''; // Character for quoting
- // literals
-static const int bulk_padding= 64; // bytes "overhead" in packet
-
-/* Federated storage engine handlerton */
-
-handlerton federated_hton= {
- "FEDERATED",
- SHOW_OPTION_YES,
- "Federated MySQL storage engine",
- DB_TYPE_FEDERATED_DB,
- federated_db_init,
- 0, /* slot */
- 0, /* savepoint size. */
- NULL, /* close_connection */
- NULL, /* savepoint */
- NULL, /* rollback to savepoint */
- NULL, /* release savepoint */
- NULL, /* commit */
- NULL, /* rollback */
- NULL, /* prepare */
- NULL, /* recover */
- NULL, /* commit_by_xid */
- NULL, /* rollback_by_xid */
- NULL, /* create_cursor_read_view */
- NULL, /* set_cursor_read_view */
- NULL, /* close_cursor_read_view */
- HTON_ALTER_NOT_SUPPORTED
-};
-
-
-/* Function we use in the creation of our hash to get key. */
-
-static byte *federated_get_key(FEDERATED_SHARE *share, uint *length,
- my_bool not_used __attribute__ ((unused)))
-{
- *length= share->connect_string_length;
- return (byte*) share->scheme;
-}
-
-/*
- Initialize the federated handler.
-
- SYNOPSIS
- federated_db_init()
- void
-
- RETURN
- FALSE OK
- TRUE Error
-*/
-
-bool federated_db_init()
-{
- DBUG_ENTER("federated_db_init");
- /* the federated engine can be disabled by a command line option */
- if (have_federated_db == SHOW_OPTION_DISABLED)
- DBUG_RETURN(TRUE);
- if (pthread_mutex_init(&federated_mutex, MY_MUTEX_INIT_FAST))
- goto error;
- if (hash_init(&federated_open_tables, &my_charset_bin, 32, 0, 0,
- (hash_get_key) federated_get_key, 0, 0))
- {
- VOID(pthread_mutex_destroy(&federated_mutex));
- }
- else
- {
- federated_init= TRUE;
- DBUG_RETURN(FALSE);
- }
-error:
- have_federated_db= SHOW_OPTION_DISABLED; // If we couldn't use handler
- DBUG_RETURN(TRUE);
-}
-
-
-/*
- Release the federated handler.
-
- SYNOPSIS
- federated_db_end()
- void
-
- RETURN
- FALSE OK
-*/
-
-bool federated_db_end()
-{
- if (federated_init)
- {
- hash_free(&federated_open_tables);
- VOID(pthread_mutex_destroy(&federated_mutex));
- }
- federated_init= 0;
- return FALSE;
-}
-
-
-/**
- @brief Append identifiers to the string.
-
- @param[in,out] string The target string.
- @param[in] name Identifier name
- @param[in] length Length of identifier name in bytes
- @param[in] quote_char Quote char to use for quoting identifier.
-
- @return Operation Status
- @retval FALSE OK
- @retval TRUE There was an error appending to the string.
-
- @note This function is based upon the append_identifier() function
- in sql_show.cc except that quoting always occurs.
-*/
-
-static bool append_ident(String *string, const char *name, uint length,
- const char quote_char)
-{
- bool result;
- uint clen;
- const char *name_end;
- DBUG_ENTER("append_ident");
-
- if (quote_char)
- {
- string->reserve(length * 2 + 2);
- if ((result= string->append(&quote_char, 1, system_charset_info)))
- goto err;
-
- for (name_end= name+length; name < name_end; name+= clen)
- {
- uchar c= *(uchar *) name;
- if (!(clen= my_mbcharlen(system_charset_info, c)))
- clen= 1;
- if (clen == 1 && c == (uchar) quote_char &&
- (result= string->append(&quote_char, 1, system_charset_info)))
- goto err;
- if ((result= string->append(name, clen, string->charset())))
- goto err;
- }
- result= string->append(&quote_char, 1, system_charset_info);
- }
- else
- result= string->append(name, length, system_charset_info);
-
-err:
- DBUG_RETURN(result);
-}
-
-
-static int parse_url_error(FEDERATED_SHARE *share, TABLE *table, int error_num)
-{
- char buf[FEDERATED_QUERY_BUFFER_SIZE];
- int buf_len;
- DBUG_ENTER("ha_federated parse_url_error");
-
- if (share->scheme)
- {
- DBUG_PRINT("info",
- ("error: parse_url. Returning error code %d freeing share->scheme 0x%lx",
- error_num, (long) share->scheme));
- my_free((gptr) share->scheme, MYF(0));
- share->scheme= 0;
- }
- buf_len= min(table->s->connect_string.length,
- FEDERATED_QUERY_BUFFER_SIZE-1);
- strmake(buf, table->s->connect_string.str, buf_len);
- my_error(error_num, MYF(0), buf);
- DBUG_RETURN(error_num);
-}
-
-/*
- Parse connection info from table->s->connect_string
-
- SYNOPSIS
- parse_url()
- share pointer to FEDERATED share
- table pointer to current TABLE class
- table_create_flag determines what error to throw
-
- DESCRIPTION
- populates the share with information about the connection
- to the foreign database that will serve as the data source.
- This string must be specified (currently) in the "comment" field,
- listed in the CREATE TABLE statement.
-
- This string MUST be in the format of any of these:
-
- 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.
-
- RETURN VALUE
- 0 success
- error_num particular error code
-
-*/
-
-static int parse_url(FEDERATED_SHARE *share, TABLE *table,
- uint table_create_flag)
-{
- uint error_num= (table_create_flag ?
- ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE :
- ER_FOREIGN_DATA_STRING_INVALID);
- DBUG_ENTER("ha_federated::parse_url");
-
- share->port= 0;
- share->socket= 0;
- DBUG_PRINT("info", ("Length %d \n", table->s->connect_string.length));
- DBUG_PRINT("info", ("String %.*s \n", table->s->connect_string.length,
- table->s->connect_string.str));
- share->scheme= my_strdup_with_length(table->s->connect_string.str,
- table->s->connect_string.length,
- MYF(0));
-
- share->connect_string_length= table->s->connect_string.length;
- DBUG_PRINT("info",("parse_url alloced share->scheme 0x%lx", (long) share->scheme));
-
- /*
- remove addition of null terminator and store length
- for each string in share
- */
- if (!(share->username= strstr(share->scheme, "://")))
- goto error;
- share->scheme[share->username - share->scheme]= '\0';
-
- if (strcmp(share->scheme, "mysql") != 0)
- goto error;
-
- share->username+= 3;
-
- if (!(share->hostname= strchr(share->username, '@')))
- goto error;
-
- share->username[share->hostname - share->username]= '\0';
- share->hostname++;
-
- if ((share->password= strchr(share->username, ':')))
- {
- share->username[share->password - share->username]= '\0';
- share->password++;
- share->username= share->username;
- /* make sure there isn't an extra / or @ */
- if ((strchr(share->password, '/') || strchr(share->hostname, '@')))
- goto error;
- /*
- Found that if the string is:
- user:@hostname:port/database/table
- Then password is a null string, so set to NULL
- */
- if ((share->password[0] == '\0'))
- share->password= NULL;
- }
- else
- share->username= share->username;
-
- /* make sure there isn't an extra / or @ */
- if ((strchr(share->username, '/')) || (strchr(share->hostname, '@')))
- goto error;
-
- if (!(share->database= strchr(share->hostname, '/')))
- goto error;
- share->hostname[share->database - share->hostname]= '\0';
- share->database++;
-
- if ((share->sport= strchr(share->hostname, ':')))
- {
- share->hostname[share->sport - share->hostname]= '\0';
- share->sport++;
- if (share->sport[0] == '\0')
- share->sport= NULL;
- else
- share->port= atoi(share->sport);
- }
-
- if (!(share->table_name= strchr(share->database, '/')))
- goto error;
- share->database[share->table_name - share->database]= '\0';
- share->table_name++;
-
- share->table_name_length= (uint) strlen(share->table_name);
-
- /* make sure there's not an extra / */
- if ((strchr(share->table_name, '/')))
- goto error;
-
- /*
- If hostname is omitted, we set it to NULL. According to
- mysql_real_connect() manual:
- The value of host may be either a hostname or an IP address.
- If host is NULL or the string "localhost", a connection to the
- local host is assumed.
- */
- if (share->hostname[0] == '\0')
- share->hostname= NULL;
-
- if (!share->port)
- {
- if (!share->hostname || strcmp(share->hostname, my_localhost) == 0)
- share->socket= (char*) MYSQL_UNIX_ADDR;
- else
- share->port= MYSQL_PORT;
- }
-
- DBUG_PRINT("info",
- ("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_name));
-
- DBUG_RETURN(0);
-
-error:
- DBUG_RETURN(parse_url_error(share, table, error_num));
-}
-
-
-/*****************************************************************************
-** FEDERATED tables
-*****************************************************************************/
-
-ha_federated::ha_federated(TABLE *table_arg)
- :handler(&federated_hton, table_arg),
- mysql(0), stored_result(0)
-{
- bzero(&bulk_insert, sizeof(bulk_insert));
-}
-
-
-/*
- Convert MySQL result set row to handler internal format
-
- SYNOPSIS
- convert_row_to_internal_format()
- record Byte pointer to record
- row MySQL result set row from fetchrow()
- result Result set to use
-
- DESCRIPTION
- This method simply iterates through a row returned via fetchrow with
- values from a successful SELECT , and then stores each column's value
- in the field object via the field object pointer (pointing to the table's
- array of field object pointers). This is how the handler needs the data
- to be stored to then return results back to the user
-
- RETURN VALUE
- 0 After fields have had field values stored from record
- */
-
-uint ha_federated::convert_row_to_internal_format(byte *record,
- MYSQL_ROW row,
- MYSQL_RES *result)
-{
- ulong *lengths;
- Field **field;
- DBUG_ENTER("ha_federated::convert_row_to_internal_format");
-
- lengths= mysql_fetch_lengths(result);
-
- for (field= table->field; *field; field++)
- {
- /*
- index variable to move us through the row at the
- same iterative step as the field
- */
- size_t x= (field - table->field);
- my_ptrdiff_t old_ptr;
- old_ptr= (my_ptrdiff_t) (record - table->record[0]);
- (*field)->move_field(old_ptr);
- if (!row[x])
- {
- (*field)->set_null();
- (*field)->reset();
- }
- else
- {
- (*field)->set_notnull();
- (*field)->store(row[x], lengths[x], &my_charset_bin);
- }
- (*field)->move_field(-old_ptr);
- }
-
- DBUG_RETURN(0);
-}
-
-static bool emit_key_part_name(String *to, KEY_PART_INFO *part)
-{
- DBUG_ENTER("emit_key_part_name");
- if (append_ident(to, part->field->field_name,
- (uint) strlen(part->field->field_name), ident_quote_char))
- DBUG_RETURN(1); // Out of memory
- DBUG_RETURN(0);
-}
-
-static bool emit_key_part_element(String *to, KEY_PART_INFO *part,
- bool needs_quotes, bool is_like,
- const byte *ptr, uint len)
-{
- Field *field= part->field;
- DBUG_ENTER("emit_key_part_element");
-
- if (needs_quotes && to->append(FEDERATED_SQUOTE))
- DBUG_RETURN(1);
-
- if (part->type == HA_KEYTYPE_BIT)
- {
- char buff[STRING_BUFFER_USUAL_SIZE], *buf= buff;
-
- *buf++= '0';
- *buf++= 'x';
- buf= octet2hex(buf, (char*) ptr, len);
- if (to->append((char*) buff, (uint)(buf - buff)))
- DBUG_RETURN(1);
- }
- else if (part->key_part_flag & HA_BLOB_PART)
- {
- String blob;
- uint blob_length= uint2korr(ptr);
- blob.set_quick((char*) ptr+HA_KEY_BLOB_LENGTH,
- blob_length, &my_charset_bin);
- if (append_escaped(to, &blob))
- DBUG_RETURN(1);
- }
- else if (part->key_part_flag & HA_VAR_LENGTH_PART)
- {
- String varchar;
- uint var_length= uint2korr(ptr);
- varchar.set_quick((char*) ptr+HA_KEY_BLOB_LENGTH,
- var_length, &my_charset_bin);
- if (append_escaped(to, &varchar))
- DBUG_RETURN(1);
- }
- else
- {
- char strbuff[MAX_FIELD_WIDTH];
- String str(strbuff, sizeof(strbuff), part->field->charset()), *res;
-
- res= field->val_str(&str, (char *)ptr);
-
- if (field->result_type() == STRING_RESULT)
- {
- if (append_escaped(to, res))
- DBUG_RETURN(1);
- }
- else if (to->append(res->ptr(), res->length()))
- DBUG_RETURN(1);
- }
-
- if (is_like && to->append(FEDERATED_PERCENT))
- DBUG_RETURN(1);
-
- if (needs_quotes && to->append(FEDERATED_SQUOTE))
- DBUG_RETURN(1);
-
- DBUG_RETURN(0);
-}
-
-/*
- Create a WHERE clause based off of values in keys
- Note: This code was inspired by key_copy from key.cc
-
- SYNOPSIS
- create_where_from_key ()
- to String object to store WHERE clause
- key_info KEY struct pointer
- key byte pointer containing key
- key_length length of key
- range_type 0 - no range, 1 - min range, 2 - max range
- (see enum range_operation)
-
- DESCRIPTION
- Using iteration through all the keys via a KEY_PART_INFO pointer,
- This method 'extracts' the value of each key in the byte pointer
- *key, and for each key found, constructs an appropriate WHERE clause
-
- RETURN VALUE
- 0 After all keys have been accounted for to create the WHERE clause
- 1 No keys found
-
- Range flags Table per Timour:
-
- -----------------
- - start_key:
- * ">" -> HA_READ_AFTER_KEY
- * ">=" -> HA_READ_KEY_OR_NEXT
- * "=" -> HA_READ_KEY_EXACT
-
- - end_key:
- * "<" -> HA_READ_BEFORE_KEY
- * "<=" -> HA_READ_AFTER_KEY
-
- records_in_range:
- -----------------
- - start_key:
- * ">" -> HA_READ_AFTER_KEY
- * ">=" -> HA_READ_KEY_EXACT
- * "=" -> HA_READ_KEY_EXACT
-
- - end_key:
- * "<" -> HA_READ_BEFORE_KEY
- * "<=" -> HA_READ_AFTER_KEY
- * "=" -> HA_READ_AFTER_KEY
-
-0 HA_READ_KEY_EXACT, Find first record else error
-1 HA_READ_KEY_OR_NEXT, Record or next record
-2 HA_READ_KEY_OR_PREV, Record or previous
-3 HA_READ_AFTER_KEY, Find next rec. after key-record
-4 HA_READ_BEFORE_KEY, Find next rec. before key-record
-5 HA_READ_PREFIX, Key which as same prefix
-6 HA_READ_PREFIX_LAST, Last key with the same prefix
-7 HA_READ_PREFIX_LAST_OR_PREV, Last or prev key with the same prefix
-
-Flags that I've found:
-
-id, primary key, varchar
-
-id = 'ccccc'
-records_in_range: start_key 0 end_key 3
-read_range_first: start_key 0 end_key NULL
-
-id > 'ccccc'
-records_in_range: start_key 3 end_key NULL
-read_range_first: start_key 3 end_key NULL
-
-id < 'ccccc'
-records_in_range: start_key NULL end_key 4
-read_range_first: start_key NULL end_key 4
-
-id <= 'ccccc'
-records_in_range: start_key NULL end_key 3
-read_range_first: start_key NULL end_key 3
-
-id >= 'ccccc'
-records_in_range: start_key 0 end_key NULL
-read_range_first: start_key 1 end_key NULL
-
-id like 'cc%cc'
-records_in_range: start_key 0 end_key 3
-read_range_first: start_key 1 end_key 3
-
-id > 'aaaaa' and id < 'ccccc'
-records_in_range: start_key 3 end_key 4
-read_range_first: start_key 3 end_key 4
-
-id >= 'aaaaa' and id < 'ccccc';
-records_in_range: start_key 0 end_key 4
-read_range_first: start_key 1 end_key 4
-
-id >= 'aaaaa' and id <= 'ccccc';
-records_in_range: start_key 0 end_key 3
-read_range_first: start_key 1 end_key 3
-
-id > 'aaaaa' and id <= 'ccccc';
-records_in_range: start_key 3 end_key 3
-read_range_first: start_key 3 end_key 3
-
-numeric keys:
-
-id = 4
-index_read_idx: start_key 0 end_key NULL
-
-id > 4
-records_in_range: start_key 3 end_key NULL
-read_range_first: start_key 3 end_key NULL
-
-id >= 4
-records_in_range: start_key 0 end_key NULL
-read_range_first: start_key 1 end_key NULL
-
-id < 4
-records_in_range: start_key NULL end_key 4
-read_range_first: start_key NULL end_key 4
-
-id <= 4
-records_in_range: start_key NULL end_key 3
-read_range_first: start_key NULL end_key 3
-
-id like 4
-full table scan, select * from
-
-id > 2 and id < 8
-records_in_range: start_key 3 end_key 4
-read_range_first: start_key 3 end_key 4
-
-id >= 2 and id < 8
-records_in_range: start_key 0 end_key 4
-read_range_first: start_key 1 end_key 4
-
-id >= 2 and id <= 8
-records_in_range: start_key 0 end_key 3
-read_range_first: start_key 1 end_key 3
-
-id > 2 and id <= 8
-records_in_range: start_key 3 end_key 3
-read_range_first: start_key 3 end_key 3
-
-multi keys (id int, name varchar, other varchar)
-
-id = 1;
-records_in_range: start_key 0 end_key 3
-read_range_first: start_key 0 end_key NULL
-
-id > 4;
-id > 2 and name = '333'; remote: id > 2
-id > 2 and name > '333'; remote: id > 2
-id > 2 and name > '333' and other < 'ddd'; remote: id > 2 no results
-id > 2 and name >= '333' and other < 'ddd'; remote: id > 2 1 result
-id >= 4 and name = 'eric was here' and other > 'eeee';
-records_in_range: start_key 3 end_key NULL
-read_range_first: start_key 3 end_key NULL
-
-id >= 4;
-id >= 2 and name = '333' and other < 'ddd';
-remote: `id` >= 2 AND `name` >= '333';
-records_in_range: start_key 0 end_key NULL
-read_range_first: start_key 1 end_key NULL
-
-id < 4;
-id < 3 and name = '222' and other <= 'ccc'; remote: id < 3
-records_in_range: start_key NULL end_key 4
-read_range_first: start_key NULL end_key 4
-
-id <= 4;
-records_in_range: start_key NULL end_key 3
-read_range_first: start_key NULL end_key 3
-
-id like 4;
-full table scan
-
-id > 2 and id < 4;
-records_in_range: start_key 3 end_key 4
-read_range_first: start_key 3 end_key 4
-
-id >= 2 and id < 4;
-records_in_range: start_key 0 end_key 4
-read_range_first: start_key 1 end_key 4
-
-id >= 2 and id <= 4;
-records_in_range: start_key 0 end_key 3
-read_range_first: start_key 1 end_key 3
-
-id > 2 and id <= 4;
-id = 6 and name = 'eric was here' and other > 'eeee';
-remote: (`id` > 6 AND `name` > 'eric was here' AND `other` > 'eeee')
-AND (`id` <= 6) AND ( AND `name` <= 'eric was here')
-no results
-records_in_range: start_key 3 end_key 3
-read_range_first: start_key 3 end_key 3
-
-Summary:
-
-* If the start key flag is 0 the max key flag shouldn't even be set,
- and if it is, the query produced would be invalid.
-* Multipart keys, even if containing some or all numeric columns,
- are treated the same as non-numeric keys
-
- If the query is " = " (quotes or not):
- - records in range start key flag HA_READ_KEY_EXACT,
- end key flag HA_READ_AFTER_KEY (incorrect)
- - any other: start key flag HA_READ_KEY_OR_NEXT,
- end key flag HA_READ_AFTER_KEY (correct)
-
-* 'like' queries (of key)
- - Numeric, full table scan
- - Non-numeric
- records_in_range: start_key 0 end_key 3
- other : start_key 1 end_key 3
-
-* If the key flag is HA_READ_AFTER_KEY:
- if start_key, append >
- if end_key, append <=
-
-* If create_where_key was called by records_in_range:
-
- - if the key is numeric:
- start key flag is 0 when end key is NULL, end key flag is 3 or 4
- - if create_where_key was called by any other function:
- start key flag is 1 when end key is NULL, end key flag is 3 or 4
- - if the key is non-numeric, or multipart
- When the query is an exact match, the start key flag is 0,
- end key flag is 3 for what should be a no-range condition where
- you should have 0 and max key NULL, which it is if called by
- read_range_first
-
-Conclusion:
-
-1. Need logic to determin if a key is min or max when the flag is
-HA_READ_AFTER_KEY, and handle appending correct operator accordingly
-
-2. Need a boolean flag to pass to create_where_from_key, used in the
-switch statement. Add 1 to the flag if:
- - start key flag is HA_READ_KEY_EXACT and the end key is NULL
-
-*/
-
-bool ha_federated::create_where_from_key(String *to,
- KEY *key_info,
- const key_range *start_key,
- const key_range *end_key,
- bool from_records_in_range)
-{
- bool both_not_null=
- (start_key != NULL && end_key != NULL) ? TRUE : FALSE;
- const byte *ptr;
- uint remainder, length;
- char tmpbuff[FEDERATED_QUERY_BUFFER_SIZE];
- String tmp(tmpbuff, sizeof(tmpbuff), system_charset_info);
- const key_range *ranges[2]= { start_key, end_key };
- DBUG_ENTER("ha_federated::create_where_from_key");
-
- tmp.length(0);
- if (start_key == NULL && end_key == NULL)
- DBUG_RETURN(1);
-
- for (int i= 0; i <= 1; i++)
- {
- bool needs_quotes;
- KEY_PART_INFO *key_part;
- if (ranges[i] == NULL)
- continue;
-
- if (both_not_null)
- {
- if (i > 0)
- tmp.append(FEDERATED_CONJUNCTION);
- else
- tmp.append(FEDERATED_OPENPAREN);
- }
-
- for (key_part= key_info->key_part,
- remainder= key_info->key_parts,
- length= ranges[i]->length,
- ptr= ranges[i]->key; ;
- remainder--,
- key_part++)
- {
- Field *field= key_part->field;
- uint store_length= key_part->store_length;
- uint part_length= min(store_length, length);
- needs_quotes= 1;
- DBUG_DUMP("key, start of loop", (uchar *)ptr, length);
-
- if (key_part->null_bit)
- {
- if (*ptr++)
- {
- /*
- We got "IS [NOT] NULL" condition against nullable column. We
- distinguish between "IS NOT NULL" and "IS NULL" by flag. For
- "IS NULL", flag is set to HA_READ_KEY_EXACT.
- */
- if (emit_key_part_name(&tmp, key_part) ||
- tmp.append(ranges[i]->flag == HA_READ_KEY_EXACT ?
- FEDERATED_ISNULL : " IS NOT NULL "))
- DBUG_RETURN(1);
- /*
- We need to adjust pointer and length to be prepared for next
- key part. As well as check if this was last key part.
- */
- goto prepare_for_next_key_part;
- }
- }
-
- if (tmp.append(FEDERATED_OPENPAREN))
- DBUG_RETURN(1);
-
- switch(ranges[i]->flag) {
- case(HA_READ_KEY_EXACT):
- DBUG_PRINT("info", ("federated HA_READ_KEY_EXACT %d", i));
- if (store_length >= length ||
- !needs_quotes ||
- key_part->type == HA_KEYTYPE_BIT ||
- field->result_type() != STRING_RESULT)
- {
- if (emit_key_part_name(&tmp, key_part))
- DBUG_RETURN(1);
-
- if (from_records_in_range)
- {
- if (tmp.append(FEDERATED_GE))
- DBUG_RETURN(1);
- }
- else
- {
- if (tmp.append(FEDERATED_EQ))
- DBUG_RETURN(1);
- }
-
- if (emit_key_part_element(&tmp, key_part, needs_quotes, 0, ptr,
- part_length))
- DBUG_RETURN(1);
- }
- else
- /* LIKE */
- {
- if (emit_key_part_name(&tmp, key_part) ||
- tmp.append(FEDERATED_LIKE) ||
- emit_key_part_element(&tmp, key_part, needs_quotes, 1, ptr,
- part_length))
- DBUG_RETURN(1);
- }
- break;
- case(HA_READ_AFTER_KEY):
- DBUG_PRINT("info", ("federated HA_READ_AFTER_KEY %d", i));
- if (store_length >= length) /* end key */
- {
- if (emit_key_part_name(&tmp, key_part))
- DBUG_RETURN(1);
-
- if (i > 0) /* end key */
- {
- if (tmp.append(FEDERATED_LE))
- DBUG_RETURN(1);
- }
- else /* start key */
- {
- if (tmp.append(FEDERATED_GT))
- DBUG_RETURN(1);
- }
-
- if (emit_key_part_element(&tmp, key_part, needs_quotes, 0, ptr,
- part_length))
- {
- DBUG_RETURN(1);
- }
- break;
- }
- case(HA_READ_KEY_OR_NEXT):
- DBUG_PRINT("info", ("federated HA_READ_KEY_OR_NEXT %d", i));
- if (emit_key_part_name(&tmp, key_part) ||
- tmp.append(FEDERATED_GE) ||
- emit_key_part_element(&tmp, key_part, needs_quotes, 0, ptr,
- part_length))
- DBUG_RETURN(1);
- break;
- case(HA_READ_BEFORE_KEY):
- DBUG_PRINT("info", ("federated HA_READ_BEFORE_KEY %d", i));
- if (store_length >= length)
- {
- if (emit_key_part_name(&tmp, key_part) ||
- tmp.append(FEDERATED_LT) ||
- emit_key_part_element(&tmp, key_part, needs_quotes, 0, ptr,
- part_length))
- DBUG_RETURN(1);
- break;
- }
- case(HA_READ_KEY_OR_PREV):
- DBUG_PRINT("info", ("federated HA_READ_KEY_OR_PREV %d", i));
- if (emit_key_part_name(&tmp, key_part) ||
- tmp.append(FEDERATED_LE) ||
- emit_key_part_element(&tmp, key_part, needs_quotes, 0, ptr,
- part_length))
- DBUG_RETURN(1);
- break;
- default:
- DBUG_PRINT("info",("cannot handle flag %d", ranges[i]->flag));
- DBUG_RETURN(1);
- }
- if (tmp.append(FEDERATED_CLOSEPAREN))
- DBUG_RETURN(1);
-
-prepare_for_next_key_part:
- if (store_length >= length)
- break;
- DBUG_PRINT("info", ("remainder %d", remainder));
- DBUG_ASSERT(remainder > 1);
- length-= store_length;
- /*
- For nullable columns, null-byte is already skipped before, that is
- ptr was incremented by 1. Since store_length still counts null-byte,
- we need to subtract 1 from store_length.
- */
- ptr+= store_length - test(key_part->null_bit);
- if (tmp.append(FEDERATED_AND))
- DBUG_RETURN(1);
-
- DBUG_PRINT("info",
- ("create_where_from_key WHERE clause: %s",
- tmp.c_ptr_quick()));
- }
- }
- if (both_not_null)
- if (tmp.append(FEDERATED_CLOSEPAREN))
- DBUG_RETURN(1);
-
- if (to->append(FEDERATED_WHERE))
- DBUG_RETURN(1);
-
- if (to->append(tmp))
- DBUG_RETURN(1);
-
- DBUG_RETURN(0);
-}
-
-/*
- Example of simple lock controls. The "share" it creates is structure we will
- pass to each federated handler. Do you have to have one of these? Well, you
- have pieces that are used for locking, and they are needed to function.
-*/
-
-static FEDERATED_SHARE *get_share(const char *table_name, TABLE *table)
-{
- char *select_query;
- char query_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- Field **field;
- String query(query_buffer, sizeof(query_buffer), &my_charset_bin);
- FEDERATED_SHARE *share= NULL, tmp_share;
- /*
- In order to use this string, we must first zero it's length,
- or it will contain garbage
- */
- query.length(0);
-
- pthread_mutex_lock(&federated_mutex);
-
- if (parse_url(&tmp_share, table, 0))
- goto error;
-
- /* TODO: change tmp_share.scheme to LEX_STRING object */
- if (!(share= (FEDERATED_SHARE *) hash_search(&federated_open_tables,
- (byte*) tmp_share.scheme,
- tmp_share.
- connect_string_length)))
- {
- query.set_charset(system_charset_info);
- query.append(FEDERATED_SELECT);
- for (field= table->field; *field; field++)
- {
- append_ident(&query, (*field)->field_name,
- (uint) strlen((*field)->field_name), ident_quote_char);
- query.append(FEDERATED_COMMA);
- }
- query.length(query.length() - (uint) strlen(FEDERATED_COMMA));
- query.append(FEDERATED_FROM);
-
- tmp_share.table_name_length= (uint) strlen(tmp_share.table_name);
- append_ident(&query, tmp_share.table_name,
- tmp_share.table_name_length, ident_quote_char);
-
- if (!(share= (FEDERATED_SHARE *)
- my_multi_malloc(MYF(MY_WME),
- &share, sizeof(*share),
- &select_query, query.length()+1,
- NullS)))
- goto error;
-
- memcpy(share, &tmp_share, sizeof(tmp_share));
- memcpy(select_query, query.ptr(), query.length()+1);
-
- share->select_query= select_query;
- share->use_count= 0;
- DBUG_PRINT("info",
- ("share->select_query %s", share->select_query));
-
- if (my_hash_insert(&federated_open_tables, (byte*) share))
- goto error;
- thr_lock_init(&share->lock);
- pthread_mutex_init(&share->mutex, MY_MUTEX_INIT_FAST);
- }
- else
- {
- /*
- Free tmp_share.scheme allocated in the parse_url()
- as we found share in the hash and tmp_share isn't needed anymore.
- */
- my_free((gptr) tmp_share.scheme, MYF(MY_ALLOW_ZERO_PTR));
- }
- share->use_count++;
- pthread_mutex_unlock(&federated_mutex);
-
- return share;
-
-error:
- pthread_mutex_unlock(&federated_mutex);
- my_free((gptr) tmp_share.scheme, MYF(MY_ALLOW_ZERO_PTR));
- my_free((gptr) share, MYF(MY_ALLOW_ZERO_PTR));
- return NULL;
-}
-
-
-/*
- Free lock controls. We call this whenever we close a table.
- If the table had the last reference to the share then we
- free memory associated with it.
-*/
-
-static int free_share(FEDERATED_SHARE *share)
-{
- DBUG_ENTER("free_share");
-
- pthread_mutex_lock(&federated_mutex);
- if (!--share->use_count)
- {
- hash_delete(&federated_open_tables, (byte*) share);
- my_free((gptr) share->scheme, MYF(MY_ALLOW_ZERO_PTR));
- thr_lock_delete(&share->lock);
- VOID(pthread_mutex_destroy(&share->mutex));
- my_free((gptr) share, MYF(0));
- }
- pthread_mutex_unlock(&federated_mutex);
-
- DBUG_RETURN(0);
-}
-
-
-ha_rows ha_federated::records_in_range(uint inx, key_range *start_key,
- key_range *end_key)
-{
- /*
-
- We really want indexes to be used as often as possible, therefore
- we just need to hard-code the return value to a very low number to
- force the issue
-
-*/
- DBUG_ENTER("ha_federated::records_in_range");
- DBUG_RETURN(FEDERATED_RECORDS_IN_RANGE);
-}
-/*
- If frm_error() is called then we will use this to to find out
- what file extentions exist for the storage engine. This is
- also used by the default rename_table and delete_table method
- in handler.cc.
-*/
-
-const char **ha_federated::bas_ext() const
-{
- static const char *ext[]=
- {
- NullS
- };
- return ext;
-}
-
-
-/*
- Used for opening tables. The name will be the name of the file.
- A table is opened when it needs to be opened. For instance
- when a request comes in for a select on the table (tables are not
- open and closed for each request, they are cached).
-
- Called from handler.cc by handler::ha_open(). The server opens
- all tables by calling ha_open() which then calls the handler
- specific open().
-*/
-
-int ha_federated::open(const char *name, int mode, uint test_if_locked)
-{
- DBUG_ENTER("ha_federated::open");
-
- if (!(share= get_share(name, table)))
- DBUG_RETURN(1);
- thr_lock_data_init(&share->lock, &lock, NULL);
-
- DBUG_ASSERT(mysql == NULL);
-
- ref_length= (table->s->primary_key != MAX_KEY ?
- table->key_info[table->s->primary_key].key_length :
- table->s->reclength);
- DBUG_PRINT("info", ("ref_length: %u", ref_length));
-
- reset();
-
- DBUG_RETURN(0);
-}
-
-
-/*
- Closes a table. We call the free_share() function to free any resources
- that we have allocated in the "shared" structure.
-
- Called from sql_base.cc, sql_select.cc, and table.cc.
- In sql_select.cc it is only used to close up temporary tables or during
- the process where a temporary table is converted over to being a
- myisam table.
- For sql_base.cc look at close_data_tables().
-*/
-
-int ha_federated::close(void)
-{
- int retval;
- DBUG_ENTER("ha_federated::close");
-
- /* free the result set */
- if (stored_result)
- {
- mysql_free_result(stored_result);
- stored_result= 0;
- }
- /* Disconnect from mysql */
- mysql_close(mysql);
- mysql= NULL;
- retval= free_share(share);
- DBUG_RETURN(retval);
-
-}
-
-/*
-
- Checks if a field in a record is SQL NULL.
-
- SYNOPSIS
- field_in_record_is_null()
- table TABLE pointer, MySQL table object
- field Field pointer, MySQL field object
- record char pointer, contains record
-
- DESCRIPTION
- This method uses the record format information in table to track
- the null bit in record.
-
- RETURN VALUE
- 1 if NULL
- 0 otherwise
-*/
-
-inline uint field_in_record_is_null(TABLE *table,
- Field *field,
- char *record)
-{
- int null_offset;
- DBUG_ENTER("ha_federated::field_in_record_is_null");
-
- if (!field->null_ptr)
- DBUG_RETURN(0);
-
- null_offset= (uint) ((char*)field->null_ptr - (char*)table->record[0]);
-
- if (record[null_offset] & field->null_bit)
- DBUG_RETURN(1);
-
- DBUG_RETURN(0);
-}
-
-
-/**
- @brief Construct the INSERT statement.
-
- @details This method will construct the INSERT statement and appends it to
- the supplied query string buffer.
-
- @return
- @retval FALSE No error
- @retval TRUE Failure
-*/
-
-bool ha_federated::append_stmt_insert(String *query)
-{
- char insert_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- Field **field;
- uint tmp_length;
-
- /* The main insert query string */
- String insert_string(insert_buffer, sizeof(insert_buffer), &my_charset_bin);
- DBUG_ENTER("ha_federated::append_stmt_insert");
-
- insert_string.length(0);
-
- if (replace_duplicates)
- insert_string.append(STRING_WITH_LEN("REPLACE INTO "));
- else if (ignore_duplicates && !insert_dup_update)
- insert_string.append(STRING_WITH_LEN("INSERT IGNORE INTO "));
- else
- insert_string.append(STRING_WITH_LEN("INSERT INTO "));
- append_ident(&insert_string, share->table_name, share->table_name_length,
- ident_quote_char);
- insert_string.append(FEDERATED_OPENPAREN);
- tmp_length= insert_string.length() - (uint) strlen(FEDERATED_COMMA);
-
- /*
- loop through the field pointer array, add any fields to both the values
- list and the fields list that match the current query id
- */
- for (field= table->field; *field; field++)
- {
- /* append the field name */
- append_ident(&insert_string, (*field)->field_name,
- (uint) strlen((*field)->field_name), ident_quote_char);
-
- /* append commas between both fields and fieldnames */
- /*
- unfortunately, we can't use the logic
- if *(fields + 1) to make the following
- appends conditional because we may not append
- if the next field doesn't match the condition:
- (((*field)->query_id && (*field)->query_id == current_query_id)
- */
- insert_string.append(FEDERATED_COMMA);
- }
-
- /*
- remove trailing comma
- */
- insert_string.length(insert_string.length() - (uint) strlen(FEDERATED_COMMA));
-
- /*
- if there were no fields, we don't want to add a closing paren
- AND, we don't want to chop off the last char '('
- insert will be "INSERT INTO t1 VALUES ();"
- */
- if (insert_string.length() > tmp_length)
- {
- insert_string.append(FEDERATED_CLOSEPAREN);
- }
-
- insert_string.append(FEDERATED_VALUES);
-
- DBUG_RETURN(query->append(insert_string));
-}
-
-
-/*
- write_row() inserts a row. No extra() hint is given currently if a bulk load
- is happeneding. buf() is a byte array of data. You can use the field
- information to extract the data from the native byte array type.
- Example of this would be:
- for (Field **field=table->field ; *field ; field++)
- {
- ...
- }
-
- Called from item_sum.cc, item_sum.cc, sql_acl.cc, sql_insert.cc,
- sql_insert.cc, sql_select.cc, sql_table.cc, sql_udf.cc, and sql_update.cc.
-*/
-
-int ha_federated::write_row(byte *buf)
-{
- char values_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- char insert_field_value_buffer[STRING_BUFFER_USUAL_SIZE];
- Field **field;
- uint tmp_length;
- int error= 0;
- bool use_bulk_insert;
- bool auto_increment_update_required= (table->next_number_field != NULL);
-
- /* The string containing the values to be added to the insert */
- String values_string(values_buffer, sizeof(values_buffer), &my_charset_bin);
- /* The actual value of the field, to be added to the values_string */
- String insert_field_value_string(insert_field_value_buffer,
- sizeof(insert_field_value_buffer),
- &my_charset_bin);
- values_string.length(0);
- insert_field_value_string.length(0);
- DBUG_ENTER("ha_federated::write_row");
-
- statistic_increment(table->in_use->status_var.ha_write_count, &LOCK_status);
- if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT)
- table->timestamp_field->set_time();
-
- /*
- start both our field and field values strings
- We must disable multi-row insert for "INSERT...ON DUPLICATE KEY UPDATE"
- Ignore duplicates is always true when insert_dup_update is true.
- When replace_duplicates == TRUE, we can safely enable multi-row insert.
- When performing multi-row insert, we only collect the columns values for
- the row. The start of the statement is only created when the first
- row is copied in to the bulk_insert string.
- */
- if (!(use_bulk_insert= bulk_insert.str &&
- (!insert_dup_update || replace_duplicates)))
- append_stmt_insert(&values_string);
-
- values_string.append(FEDERATED_OPENPAREN);
- tmp_length= values_string.length();
-
- /*
- loop through the field pointer array, add any fields to both the values
- list and the fields list that match the current query id
- */
- for (field= table->field; *field; field++)
- {
- if ((*field)->is_null())
- insert_field_value_string.append(FEDERATED_NULL);
- else
- {
- (*field)->val_str(&insert_field_value_string);
- values_string.append(value_quote_char);
- insert_field_value_string.print(&values_string);
- values_string.append(value_quote_char);
-
- insert_field_value_string.length(0);
- }
-
- /* append the value */
- values_string.append(insert_field_value_string);
- insert_field_value_string.length(0);
-
- /* append commas between both fields and fieldnames */
- /*
- unfortunately, we can't use the logic
- if *(fields + 1) to make the following
- appends conditional because we may not append
- if the next field doesn't match the condition:
- (((*field)->query_id && (*field)->query_id == current_query_id)
- */
- values_string.append(FEDERATED_COMMA);
- }
-
- /*
- if there were no fields, we don't want to add a closing paren
- AND, we don't want to chop off the last char '('
- insert will be "INSERT INTO t1 VALUES ();"
- */
- if (values_string.length() > tmp_length)
- {
- /* chops off leading commas */
- values_string.length(values_string.length() - (uint) strlen(FEDERATED_COMMA));
- }
- /* we always want to append this, even if there aren't any fields */
- values_string.append(FEDERATED_CLOSEPAREN);
-
- if (use_bulk_insert)
- {
- /*
- Send the current bulk insert out if appending the current row would
- cause the statement to overflow the packet size, otherwise set
- auto_increment_update_required to FALSE as no query was executed.
- */
- if (bulk_insert.length + values_string.length() + bulk_padding >
- mysql->net.max_packet_size && bulk_insert.length)
- {
- error= real_query(bulk_insert.str, bulk_insert.length);
- bulk_insert.length= 0;
- }
- else
- auto_increment_update_required= FALSE;
-
- if (bulk_insert.length == 0)
- {
- char insert_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- String insert_string(insert_buffer, sizeof(insert_buffer),
- &my_charset_bin);
- insert_string.length(0);
- append_stmt_insert(&insert_string);
- dynstr_append_mem(&bulk_insert, insert_string.ptr(),
- insert_string.length());
- }
- else
- dynstr_append_mem(&bulk_insert, ",", 1);
-
- dynstr_append_mem(&bulk_insert, values_string.ptr(),
- values_string.length());
- }
- else
- {
- error= real_query(values_string.ptr(), values_string.length());
- }
-
- if (error)
- {
- DBUG_RETURN(stash_remote_error());
- }
- /*
- If the table we've just written a record to contains an auto_increment
- field, then store the last_insert_id() value from the foreign server
- */
- if (auto_increment_update_required)
- {
- update_auto_increment();
-
- /* mysql_insert() uses this for protocol return value */
- table->next_number_field->store(auto_increment_value, 1);
- }
-
- DBUG_RETURN(0);
-}
-
-
-/**
- @brief Prepares the storage engine for bulk inserts.
-
- @param[in] rows estimated number of rows in bulk insert
- or 0 if unknown.
-
- @details Initializes memory structures required for bulk insert.
-*/
-
-void ha_federated::start_bulk_insert(ha_rows rows)
-{
- uint page_size;
- DBUG_ENTER("ha_federated::start_bulk_insert");
-
- dynstr_free(&bulk_insert);
-
- /**
- We don't bother with bulk-insert semantics when the estimated rows == 1
- The rows value will be 0 if the server does not know how many rows
- would be inserted. This can occur when performing INSERT...SELECT
- */
-
- if (rows == 1)
- DBUG_VOID_RETURN;
-
- /*
- Make sure we have an open connection so that we know the
- maximum packet size.
- */
- if (!mysql && real_connect())
- DBUG_VOID_RETURN;
-
- page_size= (uint) my_getpagesize();
-
- if (init_dynamic_string(&bulk_insert, NULL, page_size, page_size))
- DBUG_VOID_RETURN;
-
- bulk_insert.length= 0;
- DBUG_VOID_RETURN;
-}
-
-
-/**
- @brief End bulk insert.
-
- @details This method will send any remaining rows to the remote server.
- Finally, it will deinitialize the bulk insert data structure.
-
- @return Operation status
- @retval 0 No error
- @retval != 0 Error occured at remote server. Also sets my_errno.
-*/
-
-int ha_federated::end_bulk_insert()
-{
- int error= 0;
- DBUG_ENTER("ha_federated::end_bulk_insert");
-
- if (bulk_insert.str && bulk_insert.length)
- {
- if (real_query(bulk_insert.str, bulk_insert.length))
- error= stash_remote_error();
- else
- if (table->next_number_field)
- update_auto_increment();
- }
-
- dynstr_free(&bulk_insert);
-
- DBUG_RETURN(my_errno= error);
-}
-
-
-/*
- ha_federated::update_auto_increment
-
- This method ensures that last_insert_id() works properly. What it simply does
- is calls last_insert_id() on the foreign database immediately after insert
- (if the table has an auto_increment field) and sets the insert id via
- thd->insert_id(ID) (as well as storing thd->prev_insert_id)
-*/
-void ha_federated::update_auto_increment(void)
-{
- THD *thd= current_thd;
- DBUG_ENTER("ha_federated::update_auto_increment");
-
- ha_federated::info(HA_STATUS_AUTO);
- thd->insert_id(auto_increment_value);
- DBUG_PRINT("info",("last_insert_id: %ld", (long) auto_increment_value));
-
- DBUG_VOID_RETURN;
-}
-
-int ha_federated::optimize(THD* thd, HA_CHECK_OPT* check_opt)
-{
- char query_buffer[STRING_BUFFER_USUAL_SIZE];
- String query(query_buffer, sizeof(query_buffer), &my_charset_bin);
- DBUG_ENTER("ha_federated::optimize");
-
- query.length(0);
-
- query.set_charset(system_charset_info);
- query.append(FEDERATED_OPTIMIZE);
- append_ident(&query, share->table_name, share->table_name_length,
- ident_quote_char);
-
- if (real_query(query.ptr(), query.length()))
- {
- DBUG_RETURN(stash_remote_error());
- }
-
- DBUG_RETURN(0);
-}
-
-
-int ha_federated::repair(THD* thd, HA_CHECK_OPT* check_opt)
-{
- char query_buffer[STRING_BUFFER_USUAL_SIZE];
- String query(query_buffer, sizeof(query_buffer), &my_charset_bin);
- DBUG_ENTER("ha_federated::repair");
-
- query.length(0);
-
- query.set_charset(system_charset_info);
- query.append(FEDERATED_REPAIR);
- append_ident(&query, share->table_name, share->table_name_length,
- ident_quote_char);
- if (check_opt->flags & T_QUICK)
- query.append(FEDERATED_QUICK);
- if (check_opt->flags & T_EXTEND)
- query.append(FEDERATED_EXTENDED);
- if (check_opt->sql_flags & TT_USEFRM)
- query.append(FEDERATED_USE_FRM);
-
- if (real_query(query.ptr(), query.length()))
- {
- DBUG_RETURN(stash_remote_error());
- }
-
- DBUG_RETURN(0);
-}
-
-
-/*
- Yes, update_row() does what you expect, it updates a row. old_data will have
- the previous row record in it, while new_data will have the newest data in
- it.
-
- Keep in mind that the server can do updates based on ordering if an ORDER BY
- clause was used. Consecutive ordering is not guarenteed.
- Currently new_data will not have an updated auto_increament record, or
- and updated timestamp field. You can do these for federated by doing these:
- if (table->timestamp_on_update_now)
- update_timestamp(new_row+table->timestamp_on_update_now-1);
- if (table->next_number_field && record == table->record[0])
- update_auto_increment();
-
- Called from sql_select.cc, sql_acl.cc, sql_update.cc, and sql_insert.cc.
-*/
-
-int ha_federated::update_row(const byte *old_data, byte *new_data)
-{
- /*
- This used to control how the query was built. If there was a
- primary key, the query would be built such that there was a where
- clause with only that column as the condition. This is flawed,
- because if we have a multi-part primary key, it would only use the
- first part! We don't need to do this anyway, because
- read_range_first will retrieve the correct record, which is what
- is used to build the WHERE clause. We can however use this to
- append a LIMIT to the end if there is NOT a primary key. Why do
- this? Because we only are updating one record, and LIMIT enforces
- this.
- */
- bool has_a_primary_key= (table->s->primary_key == 0 ? TRUE : FALSE);
- /*
- buffers for following strings
- */
- char field_value_buffer[STRING_BUFFER_USUAL_SIZE];
- char update_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- char where_buffer[FEDERATED_QUERY_BUFFER_SIZE];
-
- /* Work area for field values */
- String field_value(field_value_buffer, sizeof(field_value_buffer),
- &my_charset_bin);
- /* stores the update query */
- String update_string(update_buffer,
- sizeof(update_buffer),
- &my_charset_bin);
- /* stores the WHERE clause */
- String where_string(where_buffer,
- sizeof(where_buffer),
- &my_charset_bin);
- DBUG_ENTER("ha_federated::update_row");
- /*
- set string lengths to 0 to avoid misc chars in string
- */
- field_value.length(0);
- update_string.length(0);
- where_string.length(0);
-
- if (ignore_duplicates)
- update_string.append(STRING_WITH_LEN("UPDATE IGNORE "));
- else
- update_string.append(STRING_WITH_LEN("UPDATE "));
- append_ident(&update_string, share->table_name,
- share->table_name_length, ident_quote_char);
- update_string.append(FEDERATED_SET);
-
-/*
- In this loop, we want to match column names to values being inserted
- (while building INSERT statement).
-
- Iterate through table->field (new data) and share->old_field (old_data)
- using the same index to create an SQL UPDATE statement. New data is
- used to create SET field=value and old data is used to create WHERE
- field=oldvalue
- */
-
- for (Field **field= table->field; *field; field++)
- {
- size_t field_name_length= strlen((*field)->field_name);
- append_ident(&where_string, (*field)->field_name, (uint) field_name_length,
- ident_quote_char);
- append_ident(&update_string, (*field)->field_name, (uint) field_name_length,
- ident_quote_char);
- update_string.append(FEDERATED_EQ);
-
- if ((*field)->is_null())
- update_string.append(FEDERATED_NULL);
- else
- {
- /* otherwise = */
- (*field)->val_str(&field_value);
- update_string.append(value_quote_char);
- field_value.print(&update_string);
- update_string.append(value_quote_char);
- field_value.length(0);
- }
-
- if (field_in_record_is_null(table, *field, (char*) old_data))
- where_string.append(FEDERATED_ISNULL);
- else
- {
- where_string.append(FEDERATED_EQ);
- (*field)->val_str(&field_value,
- (char*) (old_data + (*field)->offset()));
- where_string.append(value_quote_char);
- field_value.print(&where_string);
- where_string.append(value_quote_char);
- field_value.length(0);
- }
-
- /*
- Only append conjunctions if we have another field in which
- to iterate
- */
- if (*(field + 1))
- {
- update_string.append(FEDERATED_COMMA);
- where_string.append(FEDERATED_AND);
- }
- }
- update_string.append(FEDERATED_WHERE);
- update_string.append(where_string);
- /*
- If this table has not a primary key, then we could possibly
- update multiple rows. We want to make sure to only update one!
- */
- if (!has_a_primary_key)
- update_string.append(FEDERATED_LIMIT1);
-
- if (real_query(update_string.ptr(), update_string.length()))
- {
- DBUG_RETURN(stash_remote_error());
- }
- DBUG_RETURN(0);
-}
-
-/*
- This will delete a row. 'buf' will contain a copy of the row to be =deleted.
- The server will call this right after the current row has been called (from
- either a previous rnd_next() or index call).
- If you keep a pointer to the last row or can access a primary key it will
- make doing the deletion quite a bit easier.
- Keep in mind that the server does no guarentee consecutive deletions.
- ORDER BY clauses can be used.
-
- Called in sql_acl.cc and sql_udf.cc to manage internal table information.
- Called in sql_delete.cc, sql_insert.cc, and sql_select.cc. In sql_select
- it is used for removing duplicates while in insert it is used for REPLACE
- calls.
-*/
-
-int ha_federated::delete_row(const byte *buf)
-{
- char delete_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- char data_buffer[FEDERATED_QUERY_BUFFER_SIZE];
-
- String delete_string(delete_buffer, sizeof(delete_buffer), &my_charset_bin);
- String data_string(data_buffer, sizeof(data_buffer), &my_charset_bin);
- DBUG_ENTER("ha_federated::delete_row");
-
- delete_string.length(0);
- delete_string.append(FEDERATED_DELETE);
- delete_string.append(FEDERATED_FROM);
- append_ident(&delete_string, share->table_name,
- share->table_name_length, ident_quote_char);
- delete_string.append(FEDERATED_WHERE);
-
- for (Field **field= table->field; *field; field++)
- {
- Field *cur_field= *field;
- data_string.length(0);
- append_ident(&delete_string, (*field)->field_name,
- (uint) strlen((*field)->field_name), ident_quote_char);
-
- if (cur_field->is_null())
- {
- delete_string.append(FEDERATED_ISNULL);
- }
- else
- {
- delete_string.append(FEDERATED_EQ);
- cur_field->val_str(&data_string);
- delete_string.append(value_quote_char);
- data_string.print(&delete_string);
- delete_string.append(value_quote_char);
- }
-
- delete_string.append(FEDERATED_AND);
- }
- delete_string.length(delete_string.length()-5); // Remove trailing AND
-
- delete_string.append(FEDERATED_LIMIT1);
- DBUG_PRINT("info",
- ("Delete sql: %s", delete_string.c_ptr_quick()));
- if (real_query(delete_string.ptr(), delete_string.length()))
- {
- DBUG_RETURN(stash_remote_error());
- }
- deleted+= (ha_rows) mysql->affected_rows;
- records-= (ha_rows) mysql->affected_rows;
- DBUG_PRINT("info",
- ("rows deleted %ld rows deleted for all time %ld",
- (long) mysql->affected_rows, (long) deleted));
-
- DBUG_RETURN(0);
-}
-
-
-/*
- Positions an index cursor to the index specified in the handle. Fetches the
- row if available. If the key value is null, begin at the first key of the
- index. This method, which is called in the case of an SQL statement having
- a WHERE clause on a non-primary key index, simply calls index_read_idx.
-*/
-
-int ha_federated::index_read(byte *buf, const byte *key,
- uint key_len, ha_rkey_function find_flag)
-{
- DBUG_ENTER("ha_federated::index_read");
-
- if (stored_result)
- mysql_free_result(stored_result);
- DBUG_RETURN(index_read_idx_with_result_set(buf, active_index, key,
- key_len, find_flag,
- &stored_result));
-}
-
-
-/*
- Positions an index cursor to the index specified in key. Fetches the
- row if any. This is only used to read whole keys.
-
- This method is called via index_read in the case of a WHERE clause using
- a primary key index OR is called DIRECTLY when the WHERE clause
- uses a PRIMARY KEY index.
-
- NOTES
- This uses an internal result set that is deleted before function
- returns. We need to be able to be calable from ha_rnd_pos()
-*/
-
-int ha_federated::index_read_idx(byte *buf, uint index, const byte *key,
- uint key_len, enum ha_rkey_function find_flag)
-{
- int retval;
- MYSQL_RES *mysql_result;
- DBUG_ENTER("ha_federated::index_read_idx");
-
- if ((retval= index_read_idx_with_result_set(buf, index, key,
- key_len, find_flag,
- &mysql_result)))
- DBUG_RETURN(retval);
- mysql_free_result(mysql_result);
- DBUG_RETURN(retval);
-}
-
-
-/*
- Create result set for rows matching query and return first row
-
- RESULT
- 0 ok In this case *result will contain the result set
- table->status == 0
- # error In this case *result will contain 0
- table->status == STATUS_NOT_FOUND
-*/
-
-int ha_federated::index_read_idx_with_result_set(byte *buf, uint index,
- const byte *key,
- uint key_len,
- ha_rkey_function find_flag,
- MYSQL_RES **result)
-{
- int retval;
- char error_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- char index_value[STRING_BUFFER_USUAL_SIZE];
- char sql_query_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- String index_string(index_value,
- sizeof(index_value),
- &my_charset_bin);
- String sql_query(sql_query_buffer,
- sizeof(sql_query_buffer),
- &my_charset_bin);
- key_range range;
- DBUG_ENTER("ha_federated::index_read_idx_with_result_set");
-
- *result= 0; // In case of errors
- index_string.length(0);
- sql_query.length(0);
- statistic_increment(table->in_use->status_var.ha_read_key_count,
- &LOCK_status);
-
- sql_query.append(share->select_query);
-
- range.key= key;
- range.length= key_len;
- range.flag= find_flag;
- create_where_from_key(&index_string,
- &table->key_info[index],
- &range,
- NULL, 0);
- sql_query.append(index_string);
-
- if (real_query(sql_query.ptr(), sql_query.length()))
- {
- my_sprintf(error_buffer, (error_buffer, "error: %d '%s'",
- mysql_errno(mysql), mysql_error(mysql)));
- retval= ER_QUERY_ON_FOREIGN_DATA_SOURCE;
- goto error;
- }
- if (!(*result= mysql_store_result(mysql)))
- {
- retval= HA_ERR_END_OF_FILE;
- goto error;
- }
- if (!(retval= read_next(buf, *result)))
- DBUG_RETURN(retval);
-
- mysql_free_result(*result);
- *result= 0;
- table->status= STATUS_NOT_FOUND;
- DBUG_RETURN(retval);
-
-error:
- table->status= STATUS_NOT_FOUND;
- my_error(retval, MYF(0), error_buffer);
- DBUG_RETURN(retval);
-}
-
-
-/*
- This method is used exlusevely by filesort() to check if we
- can create sorting buffers of necessary size.
- If the handler returns more records that it declares
- here server can just crash on filesort().
- We cannot guarantee that's not going to happen with
- the FEDERATED engine, as we have records==0 always if the
- client is a VIEW, and for the table the number of
- records can inpredictably change during execution.
- So we return maximum possible value here.
-*/
-
-ha_rows ha_federated::estimate_rows_upper_bound()
-{
- return HA_POS_ERROR;
-}
-
-
-/* Initialized at each key walk (called multiple times unlike rnd_init()) */
-
-int ha_federated::index_init(uint keynr)
-{
- DBUG_ENTER("ha_federated::index_init");
- DBUG_PRINT("info", ("table: '%s' key: %u", table->s->table_name, keynr));
- active_index= keynr;
- DBUG_RETURN(0);
-}
-
-
-/*
- Read first range
-*/
-
-int ha_federated::read_range_first(const key_range *start_key,
- const key_range *end_key,
- bool eq_range_arg, bool sorted)
-{
- char sql_query_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- int retval;
- String sql_query(sql_query_buffer,
- sizeof(sql_query_buffer),
- &my_charset_bin);
- DBUG_ENTER("ha_federated::read_range_first");
-
- DBUG_ASSERT(!(start_key == NULL && end_key == NULL));
-
- sql_query.length(0);
- sql_query.append(share->select_query);
- create_where_from_key(&sql_query,
- &table->key_info[active_index],
- start_key, end_key, 0);
-
- if (stored_result)
- {
- mysql_free_result(stored_result);
- stored_result= 0;
- }
- if (real_query(sql_query.ptr(), sql_query.length()))
- {
- retval= ER_QUERY_ON_FOREIGN_DATA_SOURCE;
- goto error;
- }
- sql_query.length(0);
-
- if (!(stored_result= mysql_store_result(mysql)))
- {
- retval= HA_ERR_END_OF_FILE;
- goto error;
- }
-
- retval= read_next(table->record[0], stored_result);
- DBUG_RETURN(retval);
-
-error:
- table->status= STATUS_NOT_FOUND;
- DBUG_RETURN(retval);
-}
-
-
-int ha_federated::read_range_next()
-{
- int retval;
- DBUG_ENTER("ha_federated::read_range_next");
- retval= rnd_next(table->record[0]);
- DBUG_RETURN(retval);
-}
-
-
-/* Used to read forward through the index. */
-int ha_federated::index_next(byte *buf)
-{
- DBUG_ENTER("ha_federated::index_next");
- statistic_increment(table->in_use->status_var.ha_read_next_count,
- &LOCK_status);
- DBUG_RETURN(read_next(buf, stored_result));
-}
-
-
-/*
- rnd_init() is called when the system wants the storage engine to do a table
- scan.
-
- This is the method that gets data for the SELECT calls.
-
- See the federated in the introduction at the top of this file to see when
- rnd_init() is called.
-
- Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc,
- sql_table.cc, and sql_update.cc.
-*/
-
-int ha_federated::rnd_init(bool scan)
-{
- DBUG_ENTER("ha_federated::rnd_init");
- /*
- The use of the 'scan' flag is incredibly important for this handler
- to work properly, especially with updates containing WHERE clauses
- using indexed columns.
-
- When the initial query contains a WHERE clause of the query using an
- indexed column, it's index_read_idx that selects the exact record from
- the foreign database.
-
- When there is NO index in the query, either due to not having a WHERE
- clause, or the WHERE clause is using columns that are not indexed, a
- 'full table scan' done by rnd_init, which in this situation simply means
- a 'select * from ...' on the foreign table.
-
- In other words, this 'scan' flag gives us the means to ensure that if
- there is an index involved in the query, we want index_read_idx to
- retrieve the exact record (scan flag is 0), and do not want rnd_init
- to do a 'full table scan' and wipe out that result set.
-
- Prior to using this flag, the problem was most apparent with updates.
-
- An initial query like 'UPDATE tablename SET anything = whatever WHERE
- indexedcol = someval', index_read_idx would get called, using a query
- constructed with a WHERE clause built from the values of index ('indexcol'
- in this case, having a value of 'someval'). mysql_store_result would
- then get called (this would be the result set we want to use).
-
- After this rnd_init (from sql_update.cc) would be called, it would then
- unecessarily call "select * from table" on the foreign table, then call
- mysql_store_result, which would wipe out the correct previous result set
- from the previous call of index_read_idx's that had the result set
- containing the correct record, hence update the wrong row!
-
- */
-
- if (scan)
- {
- if (stored_result)
- {
- mysql_free_result(stored_result);
- stored_result= 0;
- }
-
- if (real_query(share->select_query, (uint) strlen(share->select_query)))
- goto error;
-
- stored_result= mysql_store_result(mysql);
- if (!stored_result)
- goto error;
- }
- DBUG_RETURN(0);
-
-error:
- DBUG_RETURN(stash_remote_error());
-}
-
-
-int ha_federated::rnd_end()
-{
- DBUG_ENTER("ha_federated::rnd_end");
- DBUG_RETURN(index_end());
-}
-
-
-int ha_federated::index_end(void)
-{
- DBUG_ENTER("ha_federated::index_end");
- if (stored_result)
- {
- mysql_free_result(stored_result);
- stored_result= 0;
- }
- active_index= MAX_KEY;
- DBUG_RETURN(0);
-}
-
-/*
- This is called for each row of the table scan. When you run out of records
- you should return HA_ERR_END_OF_FILE. Fill buff up with the row information.
- The Field structure for the table is the key to getting data into buf
- in a manner that will allow the server to understand it.
-
- Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc,
- sql_table.cc, and sql_update.cc.
-*/
-
-int ha_federated::rnd_next(byte *buf)
-{
- DBUG_ENTER("ha_federated::rnd_next");
-
- if (stored_result == 0)
- {
- /*
- Return value of rnd_init is not always checked (see records.cc),
- so we can get here _even_ if there is _no_ pre-fetched result-set!
- TODO: fix it. We can delete this in 5.1 when rnd_init() is checked.
- */
- DBUG_RETURN(1);
- }
- DBUG_RETURN(read_next(buf, stored_result));
-}
-
-
-/*
- ha_federated::read_next
-
- reads from a result set and converts to mysql internal
- format
-
- SYNOPSIS
- field_in_record_is_null()
- buf byte pointer to record
- result mysql result set
-
- DESCRIPTION
- This method is a wrapper method that reads one record from a result
- set and converts it to the internal table format
-
- RETURN VALUE
- 1 error
- 0 no error
-*/
-
-int ha_federated::read_next(byte *buf, MYSQL_RES *result)
-{
- int retval;
- MYSQL_ROW row;
- DBUG_ENTER("ha_federated::read_next");
-
- table->status= STATUS_NOT_FOUND; // For easier return
-
- /* Fetch a row, insert it back in a row format. */
- if (!(row= mysql_fetch_row(result)))
- DBUG_RETURN(HA_ERR_END_OF_FILE);
-
- if (!(retval= convert_row_to_internal_format(buf, row, result)))
- table->status= 0;
-
- DBUG_RETURN(retval);
-}
-
-
-/*
- store reference to current row so that we can later find it for
- a re-read, update or delete.
-
- In case of federated, a reference is either a primary key or
- the whole record.
-
- Called from filesort.cc, sql_select.cc, sql_delete.cc and sql_update.cc.
-*/
-
-void ha_federated::position(const byte *record)
-{
- DBUG_ENTER("ha_federated::position");
- if (table->s->primary_key != MAX_KEY)
- key_copy(ref, (byte *)record, table->key_info + table->s->primary_key,
- ref_length);
- else
- memcpy(ref, record, ref_length);
- DBUG_VOID_RETURN;
-}
-
-
-/*
- This is like rnd_next, but you are given a position to use to determine the
- row. The position will be of the type that you stored in ref.
-
- This method is required for an ORDER BY
-
- Called from filesort.cc records.cc sql_insert.cc sql_select.cc sql_update.cc.
-*/
-
-int ha_federated::rnd_pos(byte *buf, byte *pos)
-{
- int result;
- DBUG_ENTER("ha_federated::rnd_pos");
- statistic_increment(table->in_use->status_var.ha_read_rnd_count,
- &LOCK_status);
- if (table->s->primary_key != MAX_KEY)
- {
- /* We have a primary key, so use index_read_idx to find row */
- result= index_read_idx(buf, table->s->primary_key, pos,
- ref_length, HA_READ_KEY_EXACT);
- }
- else
- {
- /* otherwise, get the old record ref as obtained in ::position */
- memcpy(buf, pos, ref_length);
- result= 0;
- }
- table->status= result ? STATUS_NOT_FOUND : 0;
- DBUG_RETURN(result);
-}
-
-
-/*
- ::info() is used to return information to the optimizer.
- Currently this table handler doesn't implement most of the fields
- really needed. SHOW also makes use of this data
- Another note, you will probably want to have the following in your
- code:
- if (records < 2)
- records = 2;
- The reason is that the server will optimize for cases of only a single
- record. If in a table scan you don't know the number of records
- it will probably be better to set records to two so you can return
- as many records as you need.
- Along with records a few more variables you may wish to set are:
- records
- deleted
- data_file_length
- index_file_length
- delete_length
- check_time
- Take a look at the public variables in handler.h for more information.
-
- Called in:
- filesort.cc
- ha_heap.cc
- item_sum.cc
- opt_sum.cc
- sql_delete.cc
- sql_delete.cc
- sql_derived.cc
- sql_select.cc
- sql_select.cc
- sql_select.cc
- sql_select.cc
- sql_select.cc
- sql_show.cc
- sql_show.cc
- sql_show.cc
- sql_show.cc
- sql_table.cc
- sql_union.cc
- sql_update.cc
-
-*/
-
-int ha_federated::info(uint flag)
-{
- char error_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- char status_buf[FEDERATED_QUERY_BUFFER_SIZE];
- int error;
- uint error_code;
- MYSQL_RES *result= 0;
- MYSQL_ROW row;
- String status_query_string(status_buf, sizeof(status_buf), &my_charset_bin);
- DBUG_ENTER("ha_federated::info");
-
- error_code= ER_QUERY_ON_FOREIGN_DATA_SOURCE;
- /* we want not to show table status if not needed to do so */
- if (flag & (HA_STATUS_VARIABLE | HA_STATUS_CONST))
- {
- status_query_string.length(0);
- status_query_string.append(FEDERATED_INFO);
- append_ident(&status_query_string, share->table_name,
- share->table_name_length, value_quote_char);
-
- if (real_query(status_query_string.ptr(), status_query_string.length()))
- goto error;
-
- status_query_string.length(0);
-
- result= mysql_store_result(mysql);
-
- /*
- We're going to use fields num. 4, 12 and 13 of the resultset,
- so make sure we have these fields.
- */
- if (!result || (mysql_num_fields(result) < 14))
- goto error;
-
- if (!mysql_num_rows(result))
- goto error;
-
- if (!(row= mysql_fetch_row(result)))
- goto error;
-
- if (flag & HA_STATUS_VARIABLE | HA_STATUS_CONST)
- {
- /*
- deleted is set in ha_federated::info
- */
- /*
- need to figure out what this means as far as federated is concerned,
- since we don't have a "file"
-
- data_file_length = ?
- index_file_length = ?
- delete_length = ?
- */
- if (row[4] != NULL)
- records= (ha_rows) my_strtoll10(row[4], (char**) 0, &error);
-
- mean_rec_length= table->s->reclength;
- data_file_length= records * mean_rec_length;
-
- if (row[12] != NULL)
- update_time= (time_t) my_strtoll10(row[12], (char**) 0, &error);
- if (row[13] != NULL)
- check_time= (time_t) my_strtoll10(row[13], (char**) 0, &error);
- }
-
- /*
- size of IO operations (This is based on a good guess, no high science
- involved)
- */
- block_size= 4096;
- }
-
- if (flag & HA_STATUS_AUTO)
- auto_increment_value= mysql->last_used_con->insert_id;
-
- mysql_free_result(result);
-
- DBUG_RETURN(0);
-
-error:
- mysql_free_result(result);
- if (mysql)
- {
- my_sprintf(error_buffer, (error_buffer, ": %d : %s",
- mysql_errno(mysql), mysql_error(mysql)));
- my_error(error_code, MYF(0), error_buffer);
- }
- else
- if (remote_error_number != -1 /* error already reported */)
- {
- error_code= remote_error_number;
- my_error(error_code, MYF(0), ER(error_code));
- }
- DBUG_RETURN(error_code);
-}
-
-
-/**
- @brief Handles extra signals from MySQL server
-
- @param[in] operation Hint for storage engine
-
- @return Operation Status
- @retval 0 OK
- */
-int ha_federated::extra(ha_extra_function operation)
-{
- DBUG_ENTER("ha_federated::extra");
- switch (operation) {
- case HA_EXTRA_IGNORE_DUP_KEY:
- ignore_duplicates= TRUE;
- break;
- case HA_EXTRA_NO_IGNORE_DUP_KEY:
- insert_dup_update= FALSE;
- ignore_duplicates= FALSE;
- break;
- case HA_EXTRA_WRITE_CAN_REPLACE:
- replace_duplicates= TRUE;
- break;
- case HA_EXTRA_WRITE_CANNOT_REPLACE:
- /*
- We use this flag to ensure that we do not create an "INSERT IGNORE"
- statement when inserting new rows into the remote table.
- */
- replace_duplicates= FALSE;
- break;
- case HA_EXTRA_INSERT_WITH_UPDATE:
- insert_dup_update= TRUE;
- break;
- case HA_EXTRA_RESET:
- insert_dup_update= FALSE;
- ignore_duplicates= FALSE;
- replace_duplicates= FALSE;
- break;
- default:
- /* do nothing */
- DBUG_PRINT("info",("unhandled operation: %d", (uint) operation));
- }
- DBUG_RETURN(0);
-}
-
-
-/*
- Used to delete all rows in a table. Both for cases of truncate and
- for cases where the optimizer realizes that all rows will be
- removed as a result of a SQL statement.
-
- Called from item_sum.cc by Item_func_group_concat::clear(),
- Item_sum_count_distinct::clear(), and Item_func_group_concat::clear().
- Called from sql_delete.cc by mysql_delete().
- Called from sql_select.cc by JOIN::reinit().
- Called from sql_union.cc by st_select_lex_unit::exec().
-*/
-
-int ha_federated::delete_all_rows()
-{
- char query_buffer[FEDERATED_QUERY_BUFFER_SIZE];
- String query(query_buffer, sizeof(query_buffer), &my_charset_bin);
- DBUG_ENTER("ha_federated::delete_all_rows");
-
- query.length(0);
-
- query.set_charset(system_charset_info);
- query.append(FEDERATED_TRUNCATE);
- append_ident(&query, share->table_name, share->table_name_length,
- ident_quote_char);
-
- /*
- TRUNCATE won't return anything in mysql_affected_rows
- */
- if (real_query(query.ptr(), query.length()))
- {
- DBUG_RETURN(stash_remote_error());
- }
- deleted+= records;
- records= 0;
- DBUG_RETURN(0);
-}
-
-
-/*
- The idea with handler::store_lock() is the following:
-
- The statement decided which locks we should need for the table
- for updates/deletes/inserts we get WRITE locks, for SELECT... we get
- read locks.
-
- Before adding the lock into the table lock handler (see thr_lock.c)
- mysqld calls store lock with the requested locks. Store lock can now
- modify a write lock to a read lock (or some other lock), ignore the
- lock (if we don't want to use MySQL table locks at all) or add locks
- for many tables (like we do when we are using a MERGE handler).
-
- Berkeley DB for federated changes all WRITE locks to TL_WRITE_ALLOW_WRITE
- (which signals that we are doing WRITES, but we are still allowing other
- reader's and writer's.
-
- When releasing locks, store_lock() are also called. In this case one
- usually doesn't have to do anything.
-
- In some exceptional cases MySQL may send a request for a TL_IGNORE;
- This means that we are requesting the same lock as last time and this
- should also be ignored. (This may happen when someone does a flush
- table when we have opened a part of the tables, in which case mysqld
- closes and reopens the tables and tries to get the same locks at last
- time). In the future we will probably try to remove this.
-
- Called from lock.cc by get_lock_data().
-*/
-
-THR_LOCK_DATA **ha_federated::store_lock(THD *thd,
- THR_LOCK_DATA **to,
- enum thr_lock_type lock_type)
-{
- DBUG_ENTER("ha_federated::store_lock");
- if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK)
- {
- /*
- Here is where we get into the guts of a row level lock.
- If TL_UNLOCK is set
- If we are not doing a LOCK TABLE or DISCARD/IMPORT
- TABLESPACE, then allow multiple writers
- */
-
- if ((lock_type >= TL_WRITE_CONCURRENT_INSERT &&
- lock_type <= TL_WRITE) && !thd->in_lock_tables)
- lock_type= TL_WRITE_ALLOW_WRITE;
-
- /*
- In queries of type INSERT INTO t1 SELECT ... FROM t2 ...
- MySQL would use the lock TL_READ_NO_INSERT on t2, and that
- would conflict with TL_WRITE_ALLOW_WRITE, blocking all inserts
- to t2. Convert the lock to a normal read lock to allow
- concurrent inserts to t2.
- */
-
- if (lock_type == TL_READ_NO_INSERT && !thd->in_lock_tables)
- lock_type= TL_READ;
-
- lock.type= lock_type;
- }
-
- *to++= &lock;
-
- DBUG_RETURN(to);
-}
-
-/*
- create() does nothing, since we have no local setup of our own.
- FUTURE: We should potentially connect to the foreign database and
-*/
-
-int ha_federated::create(const char *name, TABLE *table_arg,
- HA_CREATE_INFO *create_info)
-{
- int retval;
- FEDERATED_SHARE tmp_share; // Only a temporary share, to test the url
- DBUG_ENTER("ha_federated::create");
-
- retval= parse_url(&tmp_share, table_arg, 1);
-
- my_free((gptr) tmp_share.scheme, MYF(MY_ALLOW_ZERO_PTR));
- DBUG_RETURN(retval);
-
-}
-
-
-int ha_federated::real_connect()
-{
- char buffer[FEDERATED_QUERY_BUFFER_SIZE];
- String sql_query(buffer, sizeof(buffer), &my_charset_bin);
- DBUG_ENTER("ha_federated::real_connect");
-
- /*
- Bug#25679
- Ensure that we do not hold the LOCK_open mutex while attempting
- to establish Federated connection to guard against a trivial
- Denial of Service scenerio.
- */
- safe_mutex_assert_not_owner(&LOCK_open);
-
- DBUG_ASSERT(mysql == NULL);
-
- if (!(mysql= mysql_init(NULL)))
- {
- remote_error_number= HA_ERR_OUT_OF_MEM;
- DBUG_RETURN(-1);
- }
-
- /*
- BUG# 17044 Federated Storage Engine is not UTF8 clean
- Add set names to whatever charset the table is at open
- of table
- */
- /* this sets the csname like 'set names utf8' */
- mysql_options(mysql,MYSQL_SET_CHARSET_NAME,
- this->table->s->table_charset->csname);
-
- sql_query.length(0);
-
- if (!mysql_real_connect(mysql,
- share->hostname,
- share->username,
- share->password,
- share->database,
- share->port,
- share->socket, 0))
- {
- stash_remote_error();
- mysql_close(mysql);
- mysql= NULL;
- my_error(ER_CONNECT_TO_FOREIGN_DATA_SOURCE, MYF(0), remote_error_buf);
- remote_error_number= -1;
- DBUG_RETURN(-1);
- }
-
- /*
- We have established a connection, lets try a simple dummy query just
- to check that the table and expected columns are present.
- */
- sql_query.append(share->select_query);
- sql_query.append(FEDERATED_WHERE);
- sql_query.append(FEDERATED_FALSE);
- if (mysql_real_query(mysql, sql_query.ptr(), sql_query.length()))
- {
- sql_query.length(0);
- sql_query.append("error: ");
- sql_query.qs_append(mysql_errno(mysql));
- sql_query.append(" '");
- sql_query.append(mysql_error(mysql));
- sql_query.append("'");
- mysql_close(mysql);
- mysql= NULL;
- my_error(ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST, MYF(0), sql_query.ptr());
- remote_error_number= -1;
- DBUG_RETURN(-1);
- }
-
- /* Just throw away the result, no rows anyways but need to keep in sync */
- mysql_free_result(mysql_store_result(mysql));
-
- /*
- Since we do not support transactions at this version, we can let the client
- API silently reconnect. For future versions, we will need more logic to
- deal with transactions
- */
-
- mysql->reconnect= 1;
- DBUG_RETURN(0);
-}
-
-
-int ha_federated::real_query(const char *query, uint length)
-{
- int rc= 0;
- DBUG_ENTER("ha_federated::real_query");
-
- if (!mysql && (rc= real_connect()))
- goto end;
-
- if (!query || !length)
- goto end;
-
- rc= mysql_real_query(mysql, query, length);
-
-end:
- DBUG_RETURN(rc);
-}
-
-
-int ha_federated::stash_remote_error()
-{
- DBUG_ENTER("ha_federated::stash_remote_error()");
- if (!mysql)
- DBUG_RETURN(remote_error_number);
- remote_error_number= mysql_errno(mysql);
- strmake(remote_error_buf, mysql_error(mysql), sizeof(remote_error_buf)-1);
- if (remote_error_number == ER_DUP_ENTRY ||
- remote_error_number == ER_DUP_KEY)
- DBUG_RETURN(HA_ERR_FOUND_DUPP_KEY);
- DBUG_RETURN(HA_FEDERATED_ERROR_WITH_REMOTE_SYSTEM);
-}
-
-
-bool ha_federated::get_error_message(int error, String* buf)
-{
- DBUG_ENTER("ha_federated::get_error_message");
- DBUG_PRINT("enter", ("error: %d", error));
- if (error == HA_FEDERATED_ERROR_WITH_REMOTE_SYSTEM)
- {
- buf->append(STRING_WITH_LEN("Error on remote system: "));
- buf->qs_append(remote_error_number);
- buf->append(STRING_WITH_LEN(": "));
- buf->append(remote_error_buf);
-
- remote_error_number= 0;
- remote_error_buf[0]= '\0';
- }
- DBUG_PRINT("exit", ("message: %s", buf->ptr()));
- DBUG_RETURN(FALSE);
-}
-
-#endif /* HAVE_FEDERATED_DB */