1 /* Copyright (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved.
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 of the License.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
12 You should have received a copy of the GNU General Public License
13 along with this program; if not, write to the Free Software
14 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
18 MySQL Federated Storage Engine
20 ha_federated.cc - MySQL Federated Storage Engine
21 Patrick Galbraith and Brian Aker, 2004
23 This is a handler which uses a foreign database as the data file, as
24 opposed to a handler like MyISAM, which uses .MYD files locally.
26 How this handler works
27 ----------------------------------
28 Normal database files are local and as such: You create a table called
29 'users', a file such as 'users.MYD' is created. A handler reads, inserts,
30 deletes, updates data in this file. The data is stored in particular format,
31 so to read, that data has to be parsed into fields, to write, fields have to
32 be stored in this format to write to this data file.
34 With MySQL Federated storage engine, there will be no local files
35 for each table's data (such as .MYD). A foreign database will store
36 the data that would normally be in this file. This will necessitate
37 the use of MySQL client API to read, delete, update, insert this
38 data. The data will have to be retrieve via an SQL call "SELECT *
39 FROM users". Then, to read this data, it will have to be retrieved
40 via mysql_fetch_row one row at a time, then converted from the
41 column in this select into the format that the handler expects.
43 The create table will simply create the .frm file, and within the
44 "CREATE TABLE" SQL, there SHALL be any of the following :
46 connection=scheme://username:password@hostname:port/database/tablename
47 connection=scheme://username@hostname/database/tablename
48 connection=scheme://username:password@hostname/database/tablename
49 connection=scheme://username:password@hostname/database/tablename
53 As of 5.1 (See worklog #3031), federated now allows you to use a non-url
54 format, taking advantage of mysql.servers:
56 connection="connection_one"
57 connection="connection_one/table_foo"
61 connection=mysql://username:password@hostname:port/database/tablename
65 create server 'server_one' foreign data wrapper 'mysql' options
74 CREATE TABLE federated.t1 (
75 `id` int(20) NOT NULL,
76 `name` varchar(64) NOT NULL default ''
78 ENGINE="FEDERATED" DEFAULT CHARSET=latin1
79 CONNECTION='server_one';
81 So, this will have been the equivalent of
83 CONNECTION="mysql://root@127.0.0.1:3306/db1/t1"
85 Then, we can also change the server to point to a new schema:
87 ALTER SERVER 'server_one' options(DATABASE 'db2');
89 All subsequent calls will now be against db2.t1! Guess what? You don't
90 have to perform an alter table!
92 This connecton="connection string" is necessary for the handler to be
93 able to connect to the foreign server, either by URL, or by server
97 The basic flow is this:
99 SQL calls issues locally ->
100 mysql handler API (data in handler format) ->
101 mysql client API (data converted to SQL calls) ->
102 foreign database -> mysql client API ->
103 convert result sets (if any) to handler format ->
104 handler API -> results or rows affected to local
106 What this handler does and doesn't support
107 ------------------------------------------
108 * Tables MUST be created on the foreign server prior to any action on those
109 tables via the handler, first version. IMPORTANT: IF you MUST use the
110 federated storage engine type on the REMOTE end, MAKE SURE [ :) ] That
111 the table you connect to IS NOT a table pointing BACK to your ORIGNAL
112 table! You know and have heard the screaching of audio feedback? You
113 know putting two mirror in front of each other how the reflection
114 continues for eternity? Well, need I say more?!
115 * There will not be support for transactions.
116 * There is no way for the handler to know if the foreign database or table
117 has changed. The reason for this is that this database has to work like a
118 data file that would never be written to by anything other than the
119 database. The integrity of the data in the local table could be breached
120 if there was any change to the foreign database.
121 * Support for SELECT, INSERT, UPDATE , DELETE, indexes.
122 * No ALTER TABLE, DROP TABLE or any other Data Definition Language calls.
123 * Prepared statements will not be used in the first implementation, it
124 remains to to be seen whether the limited subset of the client API for the
125 server supports this.
126 * This uses SELECT, INSERT, UPDATE, DELETE and not HANDLER for its
128 * This will not work with the query cache.
132 A two column table, with one record:
138 ha_federated::scan_time:
139 ha_federated::rnd_init: share->select_query SELECT * FROM foo
142 <for every row of data retrieved>
143 ha_federated::rnd_next
144 ha_federated::convert_row_to_internal_format
145 ha_federated::rnd_next
146 </for every row of data retrieved>
148 ha_federated::rnd_end
154 "INSERT INTO foo (id, ts) VALUES (2, now());"
156 ha_federated::write_row
162 "UPDATE foo SET ts = now() WHERE id = 1;"
164 ha_federated::index_init
165 ha_federated::index_read
166 ha_federated::index_read_idx
167 ha_federated::rnd_next
168 ha_federated::convert_row_to_internal_format
169 ha_federated::update_row
174 ha_federated::external_lock
178 How do I use this handler?
179 --------------------------
180 First of all, you need to build this storage engine:
182 ./configure --with-federated-storage-engine
185 Next, to use this handler, it's very simple. You must
186 have two databases running, either both on the same host, or
189 One the server that will be connecting to the foreign
190 host (client), you create your table as such:
192 CREATE TABLE test_table (
193 id int(20) NOT NULL auto_increment,
194 name varchar(32) NOT NULL default '',
195 other int(20) NOT NULL default '0',
198 KEY other_key (other))
200 DEFAULT CHARSET=latin1
201 CONNECTION='mysql://root@127.0.0.1:9306/federated/test_federated';
203 Notice the "COMMENT" and "ENGINE" field? This is where you
204 respectively set the engine type, "FEDERATED" and foreign
205 host information, this being the database your 'client' database
206 will connect to and use as the "data file". Obviously, the foreign
207 database is running on port 9306, so you want to start up your other
208 database so that it is indeed on port 9306, and your federated
209 database on a port other than that. In my setup, I use port 5554
210 for federated, and port 5555 for the foreign database.
212 Then, on the foreign database:
214 CREATE TABLE test_table (
215 id int(20) NOT NULL auto_increment,
216 name varchar(32) NOT NULL default '',
217 other int(20) NOT NULL default '0',
220 KEY other_key (other))
221 ENGINE="<NAME>" <-- whatever you want, or not specify
222 DEFAULT CHARSET=latin1 ;
224 This table is exactly the same (and must be exactly the same),
225 except that it is not using the federated handler and does
229 How to see the handler in action
230 --------------------------------
232 When developing this handler, I compiled the federated database with
235 ./configure --with-federated-storage-engine
236 --prefix=/home/mysql/mysql-build/federated/ --with-debug
238 Once compiled, I did a 'make install' (not for the purpose of installing
239 the binary, but to install all the files the binary expects to see in the
240 diretory I specified in the build with --prefix,
241 "/home/mysql/mysql-build/federated".
243 Then, I started the foreign server:
245 /usr/local/mysql/bin/mysqld_safe
246 --user=mysql --log=/tmp/mysqld.5555.log -P 5555
248 Then, I went back to the directory containing the newly compiled mysqld,
249 <builddir>/sql/, started up gdb:
253 Then, withn the (gdb) prompt:
254 (gdb) run --gdb --port=5554 --socket=/tmp/mysqld.5554 --skip-innodb --debug
256 Next, I open several windows for each:
258 1. Tail the debug trace: tail -f /tmp/mysqld.trace|grep ha_fed
259 2. Tail the SQL calls to the foreign database: tail -f /tmp/mysqld.5555.log
260 3. A window with a client open to the federated server on port 5554
261 4. A window with a client open to the federated server on port 5555
263 I would create a table on the client to the foreign server on port
264 5555, and then to the federated server on port 5554. At this point,
265 I would run whatever queries I wanted to on the federated server,
266 just always remembering that whatever changes I wanted to make on
267 the table, or if I created new tables, that I would have to do that
268 on the foreign server.
270 Another thing to look for is 'show variables' to show you that you have
271 support for federated handler support:
273 show variables like '%federat%'
277 show storage engines;
279 Both should display the federated storage handler.
285 There is a test for MySQL Federated Storage Handler in ./mysql-test/t,
286 federatedd.test It starts both a slave and master database using
287 the same setup that the replication tests use, with the exception that
288 it turns off replication, and sets replication to ignore the test tables.
289 After ensuring that you actually do have support for the federated storage
290 handler, numerous queries/inserts/updates/deletes are run, many derived
291 from the MyISAM tests, plus som other tests which were meant to reveal
292 any issues that would be most likely to affect this handler. All tests
295 To run these tests, go into ./mysql-test (based in the directory you
298 ./mysql-test-run federated
300 To run the test, or if you want to run the test and have debug info:
302 ./mysql-test-run --debug federated
304 This will run the test in debug mode, and you can view the trace and
305 log files in the ./mysql-test/var/log directory
307 ls -l mysql-test/var/log/
308 -rw-r--r-- 1 patg patg 17 4 Dec 12:27 current_test
309 -rw-r--r-- 1 patg patg 692 4 Dec 12:52 manager.log
310 -rw-rw---- 1 patg patg 21246 4 Dec 12:51 master-bin.000001
311 -rw-rw---- 1 patg patg 68 4 Dec 12:28 master-bin.index
312 -rw-r--r-- 1 patg patg 1620 4 Dec 12:51 master.err
313 -rw-rw---- 1 patg patg 23179 4 Dec 12:51 master.log
314 -rw-rw---- 1 patg patg 16696550 4 Dec 12:51 master.trace
315 -rw-r--r-- 1 patg patg 0 4 Dec 12:28 mysqltest-time
316 -rw-r--r-- 1 patg patg 2024051 4 Dec 12:51 mysqltest.trace
317 -rw-rw---- 1 patg patg 94992 4 Dec 12:51 slave-bin.000001
318 -rw-rw---- 1 patg patg 67 4 Dec 12:28 slave-bin.index
319 -rw-rw---- 1 patg patg 249 4 Dec 12:52 slave-relay-bin.000003
320 -rw-rw---- 1 patg patg 73 4 Dec 12:28 slave-relay-bin.index
321 -rw-r--r-- 1 patg patg 1349 4 Dec 12:51 slave.err
322 -rw-rw---- 1 patg patg 96206 4 Dec 12:52 slave.log
323 -rw-rw---- 1 patg patg 15706355 4 Dec 12:51 slave.trace
324 -rw-r--r-- 1 patg patg 0 4 Dec 12:51 warnings
326 Of course, again, you can tail the trace log:
328 tail -f mysql-test/var/log/master.trace |grep ha_fed
330 As well as the slave query log:
332 tail -f mysql-test/var/log/slave.log
334 Files that comprise the test suit
335 ---------------------------------
336 mysql-test/t/federated.test
337 mysql-test/r/federated.result
338 mysql-test/r/have_federated_db.require
339 mysql-test/include/have_federated_db.inc
345 These were the files that were modified or created for this
346 Federated handler to work, in 5.0:
350 ./config/ac_macros/ha_federated.m4
356 ./mysql-test/mysql-test-run(.sh)
357 ./mysql-test/t/federated.test
358 ./mysql-test/r/federated.result
359 ./mysql-test/r/have_federated_db.require
360 ./mysql-test/include/have_federated_db.inc
361 ./sql/ha_federated.cc
366 my:~/mysql-build/mysql-5.1-bkbits patg$ ls storage/federated/
367 CMakeLists.txt Makefile.in ha_federated.h plug.in
368 Makefile SCCS libfederated.a
369 Makefile.am ha_federated.cc libfederated_a-ha_federated.o
374 #define MYSQL_SERVER 1
375 #include "mysql_priv.h"
376 #include <mysql/plugin.h>
378 #ifdef USE_PRAGMA_IMPLEMENTATION
379 #pragma implementation // gcc: Class implementation
382 #include "ha_federated.h"
384 #include "m_string.h"
386 #include <mysql/plugin.h>
388 /* Variables for federated share methods */
389 static HASH federated_open_tables
; // To track open tables
390 pthread_mutex_t federated_mutex
; // To init the hash
391 static char ident_quote_char
= '`'; // Character for quoting
393 static char value_quote_char
= '\''; // Character for quoting
395 static const int bulk_padding
= 64; // bytes "overhead" in packet
397 /* Variables used when chopping off trailing characters */
398 static const uint sizeof_trailing_comma
= sizeof(", ") - 1;
399 static const uint sizeof_trailing_closeparen
= sizeof(") ") - 1;
400 static const uint sizeof_trailing_and
= sizeof(" AND ") - 1;
401 static const uint sizeof_trailing_where
= sizeof(" WHERE ") - 1;
403 /* Static declaration for handerton */
404 static handler
*federated_create_handler(handlerton
*hton
,
407 static int federated_commit(handlerton
*hton
, THD
*thd
, bool all
);
408 static int federated_rollback(handlerton
*hton
, THD
*thd
, bool all
);
410 /* Federated storage engine handlerton */
412 static handler
*federated_create_handler(handlerton
*hton
,
416 return new (mem_root
) ha_federated(hton
, table
);
420 /* Function we use in the creation of our hash to get key */
422 static uchar
*federated_get_key(FEDERATED_SHARE
*share
, size_t *length
,
423 my_bool not_used
__attribute__ ((unused
)))
425 *length
= share
->share_key_length
;
426 return (uchar
*) share
->share_key
;
430 Initialize the federated handler.
441 int federated_db_init(void *p
)
443 DBUG_ENTER("federated_db_init");
444 handlerton
*federated_hton
= (handlerton
*)p
;
445 federated_hton
->state
= SHOW_OPTION_YES
;
446 federated_hton
->db_type
= DB_TYPE_FEDERATED_DB
;
447 federated_hton
->commit
= federated_commit
;
448 federated_hton
->rollback
= federated_rollback
;
449 federated_hton
->create
= federated_create_handler
;
450 federated_hton
->flags
= HTON_ALTER_NOT_SUPPORTED
| HTON_NO_PARTITION
;
453 Support for transactions disabled until WL#2952 fixes it.
454 We do it like this to avoid "defined but not used" compiler warnings.
456 federated_hton
->commit
= 0;
457 federated_hton
->rollback
= 0;
459 if (pthread_mutex_init(&federated_mutex
, MY_MUTEX_INIT_FAST
))
461 if (!hash_init(&federated_open_tables
, &my_charset_bin
, 32, 0, 0,
462 (hash_get_key
) federated_get_key
, 0, 0))
467 VOID(pthread_mutex_destroy(&federated_mutex
));
474 Release the federated handler.
483 int federated_done(void *p
)
485 hash_free(&federated_open_tables
);
486 VOID(pthread_mutex_destroy(&federated_mutex
));
493 @brief Append identifiers to the string.
495 @param[in,out] string The target string.
496 @param[in] name Identifier name
497 @param[in] length Length of identifier name in bytes
498 @param[in] quote_char Quote char to use for quoting identifier.
500 @return Operation Status
502 @retval TRUE There was an error appending to the string.
504 @note This function is based upon the append_identifier() function
505 in sql_show.cc except that quoting always occurs.
508 static bool append_ident(String
*string
, const char *name
, size_t length
,
509 const char quote_char
)
513 const char *name_end
;
514 DBUG_ENTER("append_ident");
518 string
->reserve((uint
) length
* 2 + 2);
519 if ((result
= string
->append("e_char
, 1, system_charset_info
)))
522 for (name_end
= name
+length
; name
< name_end
; name
+= clen
)
524 uchar c
= *(uchar
*) name
;
525 if (!(clen
= my_mbcharlen(system_charset_info
, c
)))
527 if (clen
== 1 && c
== (uchar
) quote_char
&&
528 (result
= string
->append("e_char
, 1, system_charset_info
)))
530 if ((result
= string
->append(name
, clen
, string
->charset())))
533 result
= string
->append("e_char
, 1, system_charset_info
);
536 result
= string
->append(name
, (uint
) length
, system_charset_info
);
543 static int parse_url_error(FEDERATED_SHARE
*share
, TABLE
*table
, int error_num
)
545 char buf
[FEDERATED_QUERY_BUFFER_SIZE
];
547 DBUG_ENTER("ha_federated parse_url_error");
549 buf_len
= min(table
->s
->connect_string
.length
,
550 FEDERATED_QUERY_BUFFER_SIZE
-1);
551 strmake(buf
, table
->s
->connect_string
.str
, buf_len
);
552 my_error(error_num
, MYF(0), buf
);
553 DBUG_RETURN(error_num
);
557 retrieve server object which contains server meta-data
558 from the system table given a server's name, set share
559 connection parameter members
561 int get_connection(MEM_ROOT
*mem_root
, FEDERATED_SHARE
*share
)
563 int error_num
= ER_FOREIGN_SERVER_DOESNT_EXIST
;
564 FOREIGN_SERVER
*server
, server_buffer
;
565 DBUG_ENTER("ha_federated::get_connection");
568 get_server_by_name() clones the server if exists and allocates
569 copies of strings in the supplied mem_root
572 get_server_by_name(mem_root
, share
->connection_string
, &server_buffer
)))
574 DBUG_PRINT("info", ("get_server_by_name returned > 0 error condition!"));
575 /* need to come up with error handling */
579 DBUG_PRINT("info", ("get_server_by_name returned server at %lx",
580 (long unsigned int) server
));
583 Most of these should never be empty strings, error handling will
584 need to be implemented. Also, is this the best way to set the share
585 members? Is there some allocation needed? In running this code, it works
586 except there are errors in the trace file of the share being overrun
587 at the address of the share.
589 share
->server_name_length
= server
->server_name_length
;
590 share
->server_name
= server
->server_name
;
591 share
->username
= server
->username
;
592 share
->password
= server
->password
;
593 share
->database
= server
->db
;
594 #ifndef I_AM_PARANOID
595 share
->port
= server
->port
> 0 && server
->port
< 65536 ?
597 share
->port
= server
->port
> 1023 && server
->port
< 65536 ?
599 (ushort
) server
->port
: MYSQL_PORT
;
600 share
->hostname
= server
->host
;
601 if (!(share
->socket
= server
->socket
) &&
602 !strcmp(share
->hostname
, my_localhost
))
603 share
->socket
= (char *) MYSQL_UNIX_ADDR
;
604 share
->scheme
= server
->scheme
;
606 DBUG_PRINT("info", ("share->username %s", share
->username
));
607 DBUG_PRINT("info", ("share->password %s", share
->password
));
608 DBUG_PRINT("info", ("share->hostname %s", share
->hostname
));
609 DBUG_PRINT("info", ("share->database %s", share
->database
));
610 DBUG_PRINT("info", ("share->port %d", share
->port
));
611 DBUG_PRINT("info", ("share->socket %s", share
->socket
));
615 my_printf_error(error_num
, "server name: '%s' doesn't exist!",
616 MYF(0), share
->connection_string
);
617 DBUG_RETURN(error_num
);
621 Parse connection info from table->s->connect_string
625 mem_root MEM_ROOT pointer for memory allocation
626 share pointer to FEDERATED share
627 table pointer to current TABLE class
628 table_create_flag determines what error to throw
631 Populates the share with information about the connection
632 to the foreign database that will serve as the data source.
633 This string must be specified (currently) in the "CONNECTION" field,
634 listed in the CREATE TABLE statement.
636 This string MUST be in the format of any of these:
638 CONNECTION="scheme://username:password@hostname:port/database/table"
639 CONNECTION="scheme://username@hostname/database/table"
640 CONNECTION="scheme://username@hostname:port/database/table"
641 CONNECTION="scheme://username:password@hostname/database/table"
645 CONNECTION="connection name"
651 CREATE TABLE t1 (id int(32))
653 CONNECTION="mysql://joe:joespass@192.168.1.111:9308/federated/testtable";
656 id int(4) NOT NULL auto_increment,
657 name varchar(32) NOT NULL,
659 ) ENGINE="FEDERATED" CONNECTION="my_conn";
662 Currently, the Federated Storage Engine only supports connecting to another
663 MySQL Database ("scheme" of "mysql"). Connections using JDBC as well as
664 other connectors are in the planning stage.
667 'password' and 'port' are both optional.
671 error_num particular error code
675 static int parse_url(MEM_ROOT
*mem_root
, FEDERATED_SHARE
*share
, TABLE
*table
,
676 uint table_create_flag
)
678 uint error_num
= (table_create_flag
?
679 ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE
:
680 ER_FOREIGN_DATA_STRING_INVALID
);
681 DBUG_ENTER("ha_federated::parse_url");
685 DBUG_PRINT("info", ("share at %lx", (long unsigned int) share
));
686 DBUG_PRINT("info", ("Length: %u", (uint
) table
->s
->connect_string
.length
));
687 DBUG_PRINT("info", ("String: '%.*s'", (int) table
->s
->connect_string
.length
,
688 table
->s
->connect_string
.str
));
689 share
->connection_string
= strmake_root(mem_root
, table
->s
->connect_string
.str
,
690 table
->s
->connect_string
.length
);
692 DBUG_PRINT("info",("parse_url alloced share->connection_string %lx",
693 (long unsigned int) share
->connection_string
));
695 DBUG_PRINT("info",("share->connection_string %s",share
->connection_string
));
697 No :// or @ in connection string. Must be a straight connection name of
698 either "servername" or "servername/tablename"
700 if ( (!strstr(share
->connection_string
, "://") &&
701 (!strchr(share
->connection_string
, '@'))))
705 ("share->connection_string %s internal format \
706 share->connection_string %lx",
707 share
->connection_string
,
708 (long unsigned int) share
->connection_string
));
710 /* ok, so we do a little parsing, but not completely! */
711 share
->parsed
= FALSE
;
713 If there is a single '/' in the connection string, this means the user is
714 specifying a table name
717 if ((share
->table_name
= strchr(share
->connection_string
, '/')))
719 share
->connection_string
[share
->table_name
- share
->connection_string
]= '\0';
721 share
->table_name_length
= (uint
) strlen(share
->table_name
);
724 ("internal format, parsed table_name share->connection_string \
725 %s share->table_name %s",
726 share
->connection_string
, share
->table_name
));
729 there better not be any more '/'s !
731 if (strchr(share
->table_name
, '/'))
736 otherwise, straight server name, use tablename of federated table
742 connection specifies everything but, resort to
743 expecting remote and foreign table names to match
745 share
->table_name
= strmake_root(mem_root
, table
->s
->table_name
.str
,
746 (share
->table_name_length
= table
->s
->table_name
.length
));
748 ("internal format, default table_name share->connection_string \
749 %s share->table_name %s",
750 share
->connection_string
, share
->table_name
));
753 if ((error_num
= get_connection(mem_root
, share
)))
759 // Add a null for later termination of table name
760 share
->connection_string
[table
->s
->connect_string
.length
]= 0;
761 share
->scheme
= share
->connection_string
;
762 DBUG_PRINT("info",("parse_url alloced share->scheme %lx",
763 (long unsigned int) share
->scheme
));
766 remove addition of null terminator and store length
767 for each string in share
769 if (!(share
->username
= strstr(share
->scheme
, "://")))
771 share
->scheme
[share
->username
- share
->scheme
]= '\0';
773 if (strcmp(share
->scheme
, "mysql") != 0)
778 if (!(share
->hostname
= strchr(share
->username
, '@')))
781 share
->username
[share
->hostname
- share
->username
]= '\0';
784 if ((share
->password
= strchr(share
->username
, ':')))
786 share
->username
[share
->password
- share
->username
]= '\0';
788 share
->username
= share
->username
;
789 /* make sure there isn't an extra / or @ */
790 if ((strchr(share
->password
, '/') || strchr(share
->hostname
, '@')))
793 Found that if the string is:
794 user:@hostname:port/db/table
795 Then password is a null string, so set to NULL
797 if ((share
->password
[0] == '\0'))
798 share
->password
= NULL
;
801 share
->username
= share
->username
;
803 /* make sure there isn't an extra / or @ */
804 if ((strchr(share
->username
, '/')) || (strchr(share
->hostname
, '@')))
807 if (!(share
->database
= strchr(share
->hostname
, '/')))
809 share
->hostname
[share
->database
- share
->hostname
]= '\0';
812 if ((share
->sport
= strchr(share
->hostname
, ':')))
814 share
->hostname
[share
->sport
- share
->hostname
]= '\0';
816 if (share
->sport
[0] == '\0')
819 share
->port
= atoi(share
->sport
);
822 if (!(share
->table_name
= strchr(share
->database
, '/')))
824 share
->database
[share
->table_name
- share
->database
]= '\0';
827 share
->table_name_length
= strlen(share
->table_name
);
829 /* make sure there's not an extra / */
830 if ((strchr(share
->table_name
, '/')))
834 If hostname is omitted, we set it to NULL. According to
835 mysql_real_connect() manual:
836 The value of host may be either a hostname or an IP address.
837 If host is NULL or the string "localhost", a connection to the
838 local host is assumed.
840 if (share
->hostname
[0] == '\0')
841 share
->hostname
= NULL
;
846 if (!share
->hostname
|| strcmp(share
->hostname
, my_localhost
) == 0)
847 share
->socket
= (char*) MYSQL_UNIX_ADDR
;
849 share
->port
= MYSQL_PORT
;
853 ("scheme: %s username: %s password: %s \
854 hostname: %s port: %d db: %s tablename: %s",
855 share
->scheme
, share
->username
, share
->password
,
856 share
->hostname
, share
->port
, share
->database
,
862 DBUG_RETURN(parse_url_error(share
, table
, error_num
));
865 /*****************************************************************************
867 *****************************************************************************/
869 ha_federated::ha_federated(handlerton
*hton
,
870 TABLE_SHARE
*table_arg
)
871 :handler(hton
, table_arg
),
872 mysql(0), stored_result(0)
875 bzero(&bulk_insert
, sizeof(bulk_insert
));
880 Convert MySQL result set row to handler internal format
883 convert_row_to_internal_format()
884 record Byte pointer to record
885 row MySQL result set row from fetchrow()
886 result Result set to use
889 This method simply iterates through a row returned via fetchrow with
890 values from a successful SELECT , and then stores each column's value
891 in the field object via the field object pointer (pointing to the table's
892 array of field object pointers). This is how the handler needs the data
893 to be stored to then return results back to the user
896 0 After fields have had field values stored from record
899 uint
ha_federated::convert_row_to_internal_format(uchar
*record
,
905 my_bitmap_map
*old_map
= dbug_tmp_use_all_columns(table
, table
->write_set
);
906 DBUG_ENTER("ha_federated::convert_row_to_internal_format");
908 lengths
= mysql_fetch_lengths(result
);
910 for (field
= table
->field
; *field
; field
++, row
++, lengths
++)
913 index variable to move us through the row at the
914 same iterative step as the field
916 my_ptrdiff_t old_ptr
;
917 old_ptr
= (my_ptrdiff_t
) (record
- table
->record
[0]);
918 (*field
)->move_field_offset(old_ptr
);
921 (*field
)->set_null();
926 if (bitmap_is_set(table
->read_set
, (*field
)->field_index
))
928 (*field
)->set_notnull();
929 (*field
)->store(*row
, *lengths
, &my_charset_bin
);
932 (*field
)->move_field_offset(-old_ptr
);
934 dbug_tmp_restore_column_map(table
->write_set
, old_map
);
938 static bool emit_key_part_name(String
*to
, KEY_PART_INFO
*part
)
940 DBUG_ENTER("emit_key_part_name");
941 if (append_ident(to
, part
->field
->field_name
,
942 strlen(part
->field
->field_name
), ident_quote_char
))
943 DBUG_RETURN(1); // Out of memory
947 static bool emit_key_part_element(String
*to
, KEY_PART_INFO
*part
,
948 bool needs_quotes
, bool is_like
,
949 const uchar
*ptr
, uint len
)
951 Field
*field
= part
->field
;
952 DBUG_ENTER("emit_key_part_element");
954 if (needs_quotes
&& to
->append(STRING_WITH_LEN("'")))
957 if (part
->type
== HA_KEYTYPE_BIT
)
959 char buff
[STRING_BUFFER_USUAL_SIZE
], *buf
= buff
;
963 buf
= octet2hex(buf
, (char*) ptr
, len
);
964 if (to
->append((char*) buff
, (uint
)(buf
- buff
)))
967 else if (part
->key_part_flag
& HA_BLOB_PART
)
970 uint blob_length
= uint2korr(ptr
);
971 blob
.set_quick((char*) ptr
+HA_KEY_BLOB_LENGTH
,
972 blob_length
, &my_charset_bin
);
973 if (append_escaped(to
, &blob
))
976 else if (part
->key_part_flag
& HA_VAR_LENGTH_PART
)
979 uint var_length
= uint2korr(ptr
);
980 varchar
.set_quick((char*) ptr
+HA_KEY_BLOB_LENGTH
,
981 var_length
, &my_charset_bin
);
982 if (append_escaped(to
, &varchar
))
987 char strbuff
[MAX_FIELD_WIDTH
];
988 String
str(strbuff
, sizeof(strbuff
), part
->field
->charset()), *res
;
990 res
= field
->val_str(&str
, ptr
);
992 if (field
->result_type() == STRING_RESULT
)
994 if (append_escaped(to
, res
))
997 else if (to
->append(res
->ptr(), res
->length()))
1001 if (is_like
&& to
->append(STRING_WITH_LEN("%")))
1004 if (needs_quotes
&& to
->append(STRING_WITH_LEN("'")))
1011 Create a WHERE clause based off of values in keys
1012 Note: This code was inspired by key_copy from key.cc
1015 create_where_from_key ()
1016 to String object to store WHERE clause
1017 key_info KEY struct pointer
1018 key byte pointer containing key
1019 key_length length of key
1020 range_type 0 - no range, 1 - min range, 2 - max range
1021 (see enum range_operation)
1024 Using iteration through all the keys via a KEY_PART_INFO pointer,
1025 This method 'extracts' the value of each key in the byte pointer
1026 *key, and for each key found, constructs an appropriate WHERE clause
1029 0 After all keys have been accounted for to create the WHERE clause
1032 Range flags Table per Timour:
1036 * ">" -> HA_READ_AFTER_KEY
1037 * ">=" -> HA_READ_KEY_OR_NEXT
1038 * "=" -> HA_READ_KEY_EXACT
1041 * "<" -> HA_READ_BEFORE_KEY
1042 * "<=" -> HA_READ_AFTER_KEY
1047 * ">" -> HA_READ_AFTER_KEY
1048 * ">=" -> HA_READ_KEY_EXACT
1049 * "=" -> HA_READ_KEY_EXACT
1052 * "<" -> HA_READ_BEFORE_KEY
1053 * "<=" -> HA_READ_AFTER_KEY
1054 * "=" -> HA_READ_AFTER_KEY
1056 0 HA_READ_KEY_EXACT, Find first record else error
1057 1 HA_READ_KEY_OR_NEXT, Record or next record
1058 2 HA_READ_KEY_OR_PREV, Record or previous
1059 3 HA_READ_AFTER_KEY, Find next rec. after key-record
1060 4 HA_READ_BEFORE_KEY, Find next rec. before key-record
1061 5 HA_READ_PREFIX, Key which as same prefix
1062 6 HA_READ_PREFIX_LAST, Last key with the same prefix
1063 7 HA_READ_PREFIX_LAST_OR_PREV, Last or prev key with the same prefix
1065 Flags that I've found:
1067 id, primary key, varchar
1070 records_in_range: start_key 0 end_key 3
1071 read_range_first: start_key 0 end_key NULL
1074 records_in_range: start_key 3 end_key NULL
1075 read_range_first: start_key 3 end_key NULL
1078 records_in_range: start_key NULL end_key 4
1079 read_range_first: start_key NULL end_key 4
1082 records_in_range: start_key NULL end_key 3
1083 read_range_first: start_key NULL end_key 3
1086 records_in_range: start_key 0 end_key NULL
1087 read_range_first: start_key 1 end_key NULL
1090 records_in_range: start_key 0 end_key 3
1091 read_range_first: start_key 1 end_key 3
1093 id > 'aaaaa' and id < 'ccccc'
1094 records_in_range: start_key 3 end_key 4
1095 read_range_first: start_key 3 end_key 4
1097 id >= 'aaaaa' and id < 'ccccc';
1098 records_in_range: start_key 0 end_key 4
1099 read_range_first: start_key 1 end_key 4
1101 id >= 'aaaaa' and id <= 'ccccc';
1102 records_in_range: start_key 0 end_key 3
1103 read_range_first: start_key 1 end_key 3
1105 id > 'aaaaa' and id <= 'ccccc';
1106 records_in_range: start_key 3 end_key 3
1107 read_range_first: start_key 3 end_key 3
1112 index_read_idx: start_key 0 end_key NULL
1115 records_in_range: start_key 3 end_key NULL
1116 read_range_first: start_key 3 end_key NULL
1119 records_in_range: start_key 0 end_key NULL
1120 read_range_first: start_key 1 end_key NULL
1123 records_in_range: start_key NULL end_key 4
1124 read_range_first: start_key NULL end_key 4
1127 records_in_range: start_key NULL end_key 3
1128 read_range_first: start_key NULL end_key 3
1131 full table scan, select * from
1134 records_in_range: start_key 3 end_key 4
1135 read_range_first: start_key 3 end_key 4
1138 records_in_range: start_key 0 end_key 4
1139 read_range_first: start_key 1 end_key 4
1142 records_in_range: start_key 0 end_key 3
1143 read_range_first: start_key 1 end_key 3
1146 records_in_range: start_key 3 end_key 3
1147 read_range_first: start_key 3 end_key 3
1149 multi keys (id int, name varchar, other varchar)
1152 records_in_range: start_key 0 end_key 3
1153 read_range_first: start_key 0 end_key NULL
1156 id > 2 and name = '333'; remote: id > 2
1157 id > 2 and name > '333'; remote: id > 2
1158 id > 2 and name > '333' and other < 'ddd'; remote: id > 2 no results
1159 id > 2 and name >= '333' and other < 'ddd'; remote: id > 2 1 result
1160 id >= 4 and name = 'eric was here' and other > 'eeee';
1161 records_in_range: start_key 3 end_key NULL
1162 read_range_first: start_key 3 end_key NULL
1165 id >= 2 and name = '333' and other < 'ddd';
1166 remote: `id` >= 2 AND `name` >= '333';
1167 records_in_range: start_key 0 end_key NULL
1168 read_range_first: start_key 1 end_key NULL
1171 id < 3 and name = '222' and other <= 'ccc'; remote: id < 3
1172 records_in_range: start_key NULL end_key 4
1173 read_range_first: start_key NULL end_key 4
1176 records_in_range: start_key NULL end_key 3
1177 read_range_first: start_key NULL end_key 3
1183 records_in_range: start_key 3 end_key 4
1184 read_range_first: start_key 3 end_key 4
1187 records_in_range: start_key 0 end_key 4
1188 read_range_first: start_key 1 end_key 4
1190 id >= 2 and id <= 4;
1191 records_in_range: start_key 0 end_key 3
1192 read_range_first: start_key 1 end_key 3
1195 id = 6 and name = 'eric was here' and other > 'eeee';
1196 remote: (`id` > 6 AND `name` > 'eric was here' AND `other` > 'eeee')
1197 AND (`id` <= 6) AND ( AND `name` <= 'eric was here')
1199 records_in_range: start_key 3 end_key 3
1200 read_range_first: start_key 3 end_key 3
1204 * If the start key flag is 0 the max key flag shouldn't even be set,
1205 and if it is, the query produced would be invalid.
1206 * Multipart keys, even if containing some or all numeric columns,
1207 are treated the same as non-numeric keys
1209 If the query is " = " (quotes or not):
1210 - records in range start key flag HA_READ_KEY_EXACT,
1211 end key flag HA_READ_AFTER_KEY (incorrect)
1212 - any other: start key flag HA_READ_KEY_OR_NEXT,
1213 end key flag HA_READ_AFTER_KEY (correct)
1215 * 'like' queries (of key)
1216 - Numeric, full table scan
1218 records_in_range: start_key 0 end_key 3
1219 other : start_key 1 end_key 3
1221 * If the key flag is HA_READ_AFTER_KEY:
1222 if start_key, append >
1223 if end_key, append <=
1225 * If create_where_key was called by records_in_range:
1227 - if the key is numeric:
1228 start key flag is 0 when end key is NULL, end key flag is 3 or 4
1229 - if create_where_key was called by any other function:
1230 start key flag is 1 when end key is NULL, end key flag is 3 or 4
1231 - if the key is non-numeric, or multipart
1232 When the query is an exact match, the start key flag is 0,
1233 end key flag is 3 for what should be a no-range condition where
1234 you should have 0 and max key NULL, which it is if called by
1239 1. Need logic to determin if a key is min or max when the flag is
1240 HA_READ_AFTER_KEY, and handle appending correct operator accordingly
1242 2. Need a boolean flag to pass to create_where_from_key, used in the
1243 switch statement. Add 1 to the flag if:
1244 - start key flag is HA_READ_KEY_EXACT and the end key is NULL
1248 bool ha_federated::create_where_from_key(String
*to
,
1250 const key_range
*start_key
,
1251 const key_range
*end_key
,
1252 bool from_records_in_range
,
1256 (start_key
!= NULL
&& end_key
!= NULL
) ? TRUE
: FALSE
;
1258 uint remainder
, length
;
1259 char tmpbuff
[FEDERATED_QUERY_BUFFER_SIZE
];
1260 String
tmp(tmpbuff
, sizeof(tmpbuff
), system_charset_info
);
1261 const key_range
*ranges
[2]= { start_key
, end_key
};
1262 my_bitmap_map
*old_map
;
1263 DBUG_ENTER("ha_federated::create_where_from_key");
1266 if (start_key
== NULL
&& end_key
== NULL
)
1269 old_map
= dbug_tmp_use_all_columns(table
, table
->write_set
);
1270 for (uint i
= 0; i
<= 1; i
++)
1273 KEY_PART_INFO
*key_part
;
1274 if (ranges
[i
] == NULL
)
1280 tmp
.append(STRING_WITH_LEN(") AND ("));
1282 tmp
.append(STRING_WITH_LEN(" ("));
1285 for (key_part
= key_info
->key_part
,
1286 remainder
= key_info
->key_parts
,
1287 length
= ranges
[i
]->length
,
1288 ptr
= ranges
[i
]->key
; ;
1292 Field
*field
= key_part
->field
;
1293 uint store_length
= key_part
->store_length
;
1294 uint part_length
= min(store_length
, length
);
1295 needs_quotes
= field
->str_needs_quotes();
1296 DBUG_DUMP("key, start of loop", ptr
, length
);
1298 if (key_part
->null_bit
)
1303 We got "IS [NOT] NULL" condition against nullable column. We
1304 distinguish between "IS NOT NULL" and "IS NULL" by flag. For
1305 "IS NULL", flag is set to HA_READ_KEY_EXACT.
1307 if (emit_key_part_name(&tmp
, key_part
) ||
1308 (ranges
[i
]->flag
== HA_READ_KEY_EXACT
?
1309 tmp
.append(STRING_WITH_LEN(" IS NULL ")) :
1310 tmp
.append(STRING_WITH_LEN(" IS NOT NULL "))))
1313 We need to adjust pointer and length to be prepared for next
1314 key part. As well as check if this was last key part.
1316 goto prepare_for_next_key_part
;
1320 if (tmp
.append(STRING_WITH_LEN(" (")))
1323 switch (ranges
[i
]->flag
) {
1324 case HA_READ_KEY_EXACT
:
1325 DBUG_PRINT("info", ("federated HA_READ_KEY_EXACT %d", i
));
1326 if (store_length
>= length
||
1328 key_part
->type
== HA_KEYTYPE_BIT
||
1329 field
->result_type() != STRING_RESULT
)
1331 if (emit_key_part_name(&tmp
, key_part
))
1334 if (from_records_in_range
)
1336 if (tmp
.append(STRING_WITH_LEN(" >= ")))
1341 if (tmp
.append(STRING_WITH_LEN(" = ")))
1345 if (emit_key_part_element(&tmp
, key_part
, needs_quotes
, 0, ptr
,
1352 if (emit_key_part_name(&tmp
, key_part
) ||
1353 tmp
.append(STRING_WITH_LEN(" LIKE ")) ||
1354 emit_key_part_element(&tmp
, key_part
, needs_quotes
, 1, ptr
,
1359 case HA_READ_AFTER_KEY
:
1362 if (tmp
.append("1=1")) // Dummy
1366 DBUG_PRINT("info", ("federated HA_READ_AFTER_KEY %d", i
));
1367 if ((store_length
>= length
) || (i
> 0)) /* for all parts of end key*/
1369 if (emit_key_part_name(&tmp
, key_part
))
1372 if (i
> 0) /* end key */
1374 if (tmp
.append(STRING_WITH_LEN(" <= ")))
1377 else /* start key */
1379 if (tmp
.append(STRING_WITH_LEN(" > ")))
1383 if (emit_key_part_element(&tmp
, key_part
, needs_quotes
, 0, ptr
,
1390 case HA_READ_KEY_OR_NEXT
:
1391 DBUG_PRINT("info", ("federated HA_READ_KEY_OR_NEXT %d", i
));
1392 if (emit_key_part_name(&tmp
, key_part
) ||
1393 tmp
.append(STRING_WITH_LEN(" >= ")) ||
1394 emit_key_part_element(&tmp
, key_part
, needs_quotes
, 0, ptr
,
1398 case HA_READ_BEFORE_KEY
:
1399 DBUG_PRINT("info", ("federated HA_READ_BEFORE_KEY %d", i
));
1400 if (store_length
>= length
)
1402 if (emit_key_part_name(&tmp
, key_part
) ||
1403 tmp
.append(STRING_WITH_LEN(" < ")) ||
1404 emit_key_part_element(&tmp
, key_part
, needs_quotes
, 0, ptr
,
1409 case HA_READ_KEY_OR_PREV
:
1410 DBUG_PRINT("info", ("federated HA_READ_KEY_OR_PREV %d", i
));
1411 if (emit_key_part_name(&tmp
, key_part
) ||
1412 tmp
.append(STRING_WITH_LEN(" <= ")) ||
1413 emit_key_part_element(&tmp
, key_part
, needs_quotes
, 0, ptr
,
1418 DBUG_PRINT("info",("cannot handle flag %d", ranges
[i
]->flag
));
1421 if (tmp
.append(STRING_WITH_LEN(") ")))
1424 prepare_for_next_key_part
:
1425 if (store_length
>= length
)
1427 DBUG_PRINT("info", ("remainder %d", remainder
));
1428 DBUG_ASSERT(remainder
> 1);
1429 length
-= store_length
;
1431 For nullable columns, null-byte is already skipped before, that is
1432 ptr was incremented by 1. Since store_length still counts null-byte,
1433 we need to subtract 1 from store_length.
1435 ptr
+= store_length
- test(key_part
->null_bit
);
1436 if (tmp
.append(STRING_WITH_LEN(" AND ")))
1440 ("create_where_from_key WHERE clause: %s",
1441 tmp
.c_ptr_quick()));
1444 dbug_tmp_restore_column_map(table
->write_set
, old_map
);
1447 if (tmp
.append(STRING_WITH_LEN(") ")))
1450 if (to
->append(STRING_WITH_LEN(" WHERE ")))
1453 if (to
->append(tmp
))
1459 dbug_tmp_restore_column_map(table
->write_set
, old_map
);
1464 Example of simple lock controls. The "share" it creates is structure we will
1465 pass to each federated handler. Do you have to have one of these? Well, you
1466 have pieces that are used for locking, and they are needed to function.
1469 static FEDERATED_SHARE
*get_share(const char *table_name
, TABLE
*table
)
1471 char query_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
1473 String
query(query_buffer
, sizeof(query_buffer
), &my_charset_bin
);
1474 FEDERATED_SHARE
*share
= NULL
, tmp_share
;
1476 DBUG_ENTER("ha_federated.cc::get_share");
1479 In order to use this string, we must first zero it's length,
1480 or it will contain garbage
1484 init_alloc_root(&mem_root
, 256, 0);
1486 pthread_mutex_lock(&federated_mutex
);
1488 tmp_share
.share_key
= table_name
;
1489 tmp_share
.share_key_length
= (uint
) strlen(table_name
);
1490 if (parse_url(&mem_root
, &tmp_share
, table
, 0))
1493 /* TODO: change tmp_share.scheme to LEX_STRING object */
1494 if (!(share
= (FEDERATED_SHARE
*) hash_search(&federated_open_tables
,
1495 (uchar
*) tmp_share
.share_key
,
1499 query
.set_charset(system_charset_info
);
1500 query
.append(STRING_WITH_LEN("SELECT "));
1501 for (field
= table
->field
; *field
; field
++)
1503 append_ident(&query
, (*field
)->field_name
,
1504 strlen((*field
)->field_name
), ident_quote_char
);
1505 query
.append(STRING_WITH_LEN(", "));
1507 /* chops off trailing comma */
1508 query
.length(query
.length() - sizeof_trailing_comma
);
1510 query
.append(STRING_WITH_LEN(" FROM "));
1512 append_ident(&query
, tmp_share
.table_name
,
1513 tmp_share
.table_name_length
, ident_quote_char
);
1515 if (!(share
= (FEDERATED_SHARE
*) memdup_root(&mem_root
, (char*)&tmp_share
, sizeof(*share
))) ||
1516 !(share
->select_query
= (char*) strmake_root(&mem_root
, query
.ptr(), query
.length() + 1)))
1519 share
->use_count
= 0;
1520 share
->mem_root
= mem_root
;
1523 ("share->select_query %s", share
->select_query
));
1525 if (my_hash_insert(&federated_open_tables
, (uchar
*) share
))
1527 thr_lock_init(&share
->lock
);
1528 pthread_mutex_init(&share
->mutex
, MY_MUTEX_INIT_FAST
);
1531 free_root(&mem_root
, MYF(0)); /* prevents memory leak */
1534 pthread_mutex_unlock(&federated_mutex
);
1539 pthread_mutex_unlock(&federated_mutex
);
1540 free_root(&mem_root
, MYF(0));
1546 Free lock controls. We call this whenever we close a table.
1547 If the table had the last reference to the share then we
1548 free memory associated with it.
1551 static int free_share(FEDERATED_SHARE
*share
)
1553 MEM_ROOT mem_root
= share
->mem_root
;
1554 DBUG_ENTER("free_share");
1556 pthread_mutex_lock(&federated_mutex
);
1557 if (!--share
->use_count
)
1559 hash_delete(&federated_open_tables
, (uchar
*) share
);
1560 thr_lock_delete(&share
->lock
);
1561 VOID(pthread_mutex_destroy(&share
->mutex
));
1562 free_root(&mem_root
, MYF(0));
1564 pthread_mutex_unlock(&federated_mutex
);
1570 ha_rows
ha_federated::records_in_range(uint inx
, key_range
*start_key
,
1575 We really want indexes to be used as often as possible, therefore
1576 we just need to hard-code the return value to a very low number to
1580 DBUG_ENTER("ha_federated::records_in_range");
1581 DBUG_RETURN(FEDERATED_RECORDS_IN_RANGE
);
1584 If frm_error() is called then we will use this to to find out
1585 what file extentions exist for the storage engine. This is
1586 also used by the default rename_table and delete_table method
1590 const char **ha_federated::bas_ext() const
1592 static const char *ext
[]=
1601 Used for opening tables. The name will be the name of the file.
1602 A table is opened when it needs to be opened. For instance
1603 when a request comes in for a select on the table (tables are not
1604 open and closed for each request, they are cached).
1606 Called from handler.cc by handler::ha_open(). The server opens
1607 all tables by calling ha_open() which then calls the handler
1611 int ha_federated::open(const char *name
, int mode
, uint test_if_locked
)
1613 DBUG_ENTER("ha_federated::open");
1615 if (!(share
= get_share(name
, table
)))
1617 thr_lock_data_init(&share
->lock
, &lock
, NULL
);
1619 DBUG_ASSERT(mysql
== NULL
);
1621 ref_length
= sizeof(MYSQL_RES
*) + sizeof(MYSQL_ROW_OFFSET
);
1622 DBUG_PRINT("info", ("ref_length: %u", ref_length
));
1624 my_init_dynamic_array(&results
, sizeof(MYSQL_RES
*), 4, 4);
1632 Closes a table. We call the free_share() function to free any resources
1633 that we have allocated in the "shared" structure.
1635 Called from sql_base.cc, sql_select.cc, and table.cc.
1636 In sql_select.cc it is only used to close up temporary tables or during
1637 the process where a temporary table is converted over to being a
1639 For sql_base.cc look at close_data_tables().
1642 int ha_federated::close(void)
1644 DBUG_ENTER("ha_federated::close");
1648 delete_dynamic(&results
);
1650 /* Disconnect from mysql */
1655 mysql_close() might return an error if a remote server's gone
1656 for some reason. If that happens while removing a table from
1657 the table cache, the error will be propagated to a client even
1658 if the original query was not issued against the FEDERATED table.
1659 So, don't propagate errors from mysql_close().
1662 table
->in_use
->clear_error();
1664 DBUG_RETURN(free_share(share
));
1669 Checks if a field in a record is SQL NULL.
1672 field_in_record_is_null()
1673 table TABLE pointer, MySQL table object
1674 field Field pointer, MySQL field object
1675 record char pointer, contains record
1678 This method uses the record format information in table to track
1679 the null bit in record.
1686 static inline uint
field_in_record_is_null(TABLE
*table
,
1691 DBUG_ENTER("ha_federated::field_in_record_is_null");
1693 if (!field
->null_ptr
)
1696 null_offset
= (uint
) ((char*)field
->null_ptr
- (char*)table
->record
[0]);
1698 if (record
[null_offset
] & field
->null_bit
)
1706 @brief Construct the INSERT statement.
1708 @details This method will construct the INSERT statement and appends it to
1709 the supplied query string buffer.
1712 @retval FALSE No error
1713 @retval TRUE Failure
1716 bool ha_federated::append_stmt_insert(String
*query
)
1718 char insert_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
1721 bool added_field
= FALSE
;
1723 /* The main insert query string */
1724 String
insert_string(insert_buffer
, sizeof(insert_buffer
), &my_charset_bin
);
1725 DBUG_ENTER("ha_federated::append_stmt_insert");
1727 insert_string
.length(0);
1729 if (replace_duplicates
)
1730 insert_string
.append(STRING_WITH_LEN("REPLACE INTO "));
1731 else if (ignore_duplicates
&& !insert_dup_update
)
1732 insert_string
.append(STRING_WITH_LEN("INSERT IGNORE INTO "));
1734 insert_string
.append(STRING_WITH_LEN("INSERT INTO "));
1735 append_ident(&insert_string
, share
->table_name
, share
->table_name_length
,
1737 tmp_length
= insert_string
.length();
1738 insert_string
.append(STRING_WITH_LEN(" ("));
1741 loop through the field pointer array, add any fields to both the values
1742 list and the fields list that match the current query id
1744 for (field
= table
->field
; *field
; field
++)
1746 if (bitmap_is_set(table
->write_set
, (*field
)->field_index
))
1748 /* append the field name */
1749 append_ident(&insert_string
, (*field
)->field_name
,
1750 strlen((*field
)->field_name
), ident_quote_char
);
1752 /* append commas between both fields and fieldnames */
1754 unfortunately, we can't use the logic if *(fields + 1) to
1755 make the following appends conditional as we don't know if the
1756 next field is in the write set
1758 insert_string
.append(STRING_WITH_LEN(", "));
1765 /* Remove trailing comma. */
1766 insert_string
.length(insert_string
.length() - sizeof_trailing_comma
);
1767 insert_string
.append(STRING_WITH_LEN(") "));
1771 /* If there were no fields, we don't want to add a closing paren. */
1772 insert_string
.length(tmp_length
);
1775 insert_string
.append(STRING_WITH_LEN(" VALUES "));
1777 DBUG_RETURN(query
->append(insert_string
));
1782 write_row() inserts a row. No extra() hint is given currently if a bulk load
1783 is happeneding. buf() is a byte array of data. You can use the field
1784 information to extract the data from the native byte array type.
1785 Example of this would be:
1786 for (Field **field=table->field ; *field ; field++)
1791 Called from item_sum.cc, item_sum.cc, sql_acl.cc, sql_insert.cc,
1792 sql_insert.cc, sql_select.cc, sql_table.cc, sql_udf.cc, and sql_update.cc.
1795 int ha_federated::write_row(uchar
*buf
)
1797 char values_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
1798 char insert_field_value_buffer
[STRING_BUFFER_USUAL_SIZE
];
1802 bool use_bulk_insert
;
1803 bool auto_increment_update_required
= (table
->next_number_field
!= NULL
);
1805 /* The string containing the values to be added to the insert */
1806 String
values_string(values_buffer
, sizeof(values_buffer
), &my_charset_bin
);
1807 /* The actual value of the field, to be added to the values_string */
1808 String
insert_field_value_string(insert_field_value_buffer
,
1809 sizeof(insert_field_value_buffer
),
1811 my_bitmap_map
*old_map
= dbug_tmp_use_all_columns(table
, table
->read_set
);
1812 DBUG_ENTER("ha_federated::write_row");
1814 values_string
.length(0);
1815 insert_field_value_string
.length(0);
1816 ha_statistic_increment(&SSV::ha_write_count
);
1817 if (table
->timestamp_field_type
& TIMESTAMP_AUTO_SET_ON_INSERT
)
1818 table
->timestamp_field
->set_time();
1821 start both our field and field values strings
1822 We must disable multi-row insert for "INSERT...ON DUPLICATE KEY UPDATE"
1823 Ignore duplicates is always true when insert_dup_update is true.
1824 When replace_duplicates == TRUE, we can safely enable multi-row insert.
1825 When performing multi-row insert, we only collect the columns values for
1826 the row. The start of the statement is only created when the first
1827 row is copied in to the bulk_insert string.
1829 if (!(use_bulk_insert
= bulk_insert
.str
&&
1830 (!insert_dup_update
|| replace_duplicates
)))
1831 append_stmt_insert(&values_string
);
1833 values_string
.append(STRING_WITH_LEN(" ("));
1834 tmp_length
= values_string
.length();
1837 loop through the field pointer array, add any fields to both the values
1838 list and the fields list that is part of the write set
1840 for (field
= table
->field
; *field
; field
++)
1842 if (bitmap_is_set(table
->write_set
, (*field
)->field_index
))
1844 if ((*field
)->is_null())
1845 values_string
.append(STRING_WITH_LEN(" NULL "));
1848 bool needs_quote
= (*field
)->str_needs_quotes();
1849 (*field
)->val_str(&insert_field_value_string
);
1851 values_string
.append(value_quote_char
);
1852 insert_field_value_string
.print(&values_string
);
1854 values_string
.append(value_quote_char
);
1856 insert_field_value_string
.length(0);
1859 /* append commas between both fields and fieldnames */
1861 unfortunately, we can't use the logic if *(fields + 1) to
1862 make the following appends conditional as we don't know if the
1863 next field is in the write set
1865 values_string
.append(STRING_WITH_LEN(", "));
1868 dbug_tmp_restore_column_map(table
->read_set
, old_map
);
1871 if there were no fields, we don't want to add a closing paren
1872 AND, we don't want to chop off the last char '('
1873 insert will be "INSERT INTO t1 VALUES ();"
1875 if (values_string
.length() > tmp_length
)
1877 /* chops off trailing comma */
1878 values_string
.length(values_string
.length() - sizeof_trailing_comma
);
1880 /* we always want to append this, even if there aren't any fields */
1881 values_string
.append(STRING_WITH_LEN(") "));
1883 if (use_bulk_insert
)
1886 Send the current bulk insert out if appending the current row would
1887 cause the statement to overflow the packet size, otherwise set
1888 auto_increment_update_required to FALSE as no query was executed.
1890 if (bulk_insert
.length
+ values_string
.length() + bulk_padding
>
1891 mysql
->net
.max_packet_size
&& bulk_insert
.length
)
1893 error
= real_query(bulk_insert
.str
, bulk_insert
.length
);
1894 bulk_insert
.length
= 0;
1897 auto_increment_update_required
= FALSE
;
1899 if (bulk_insert
.length
== 0)
1901 char insert_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
1902 String
insert_string(insert_buffer
, sizeof(insert_buffer
),
1904 insert_string
.length(0);
1905 append_stmt_insert(&insert_string
);
1906 dynstr_append_mem(&bulk_insert
, insert_string
.ptr(),
1907 insert_string
.length());
1910 dynstr_append_mem(&bulk_insert
, ",", 1);
1912 dynstr_append_mem(&bulk_insert
, values_string
.ptr(),
1913 values_string
.length());
1917 error
= real_query(values_string
.ptr(), values_string
.length());
1922 DBUG_RETURN(stash_remote_error());
1925 If the table we've just written a record to contains an auto_increment
1926 field, then store the last_insert_id() value from the foreign server
1928 if (auto_increment_update_required
)
1930 update_auto_increment();
1932 /* mysql_insert() uses this for protocol return value */
1933 table
->next_number_field
->store(stats
.auto_increment_value
, 1);
1941 @brief Prepares the storage engine for bulk inserts.
1943 @param[in] rows estimated number of rows in bulk insert
1946 @details Initializes memory structures required for bulk insert.
1949 void ha_federated::start_bulk_insert(ha_rows rows
)
1952 DBUG_ENTER("ha_federated::start_bulk_insert");
1954 dynstr_free(&bulk_insert
);
1957 We don't bother with bulk-insert semantics when the estimated rows == 1
1958 The rows value will be 0 if the server does not know how many rows
1959 would be inserted. This can occur when performing INSERT...SELECT
1966 Make sure we have an open connection so that we know the
1967 maximum packet size.
1969 if (!mysql
&& real_connect())
1972 page_size
= (uint
) my_getpagesize();
1974 if (init_dynamic_string(&bulk_insert
, NULL
, page_size
, page_size
))
1977 bulk_insert
.length
= 0;
1983 @brief End bulk insert.
1985 @details This method will send any remaining rows to the remote server.
1986 Finally, it will deinitialize the bulk insert data structure.
1988 @return Operation status
1990 @retval != 0 Error occured at remote server. Also sets my_errno.
1993 int ha_federated::end_bulk_insert()
1996 DBUG_ENTER("ha_federated::end_bulk_insert");
1998 if (bulk_insert
.str
&& bulk_insert
.length
)
2000 if (real_query(bulk_insert
.str
, bulk_insert
.length
))
2001 error
= stash_remote_error();
2003 if (table
->next_number_field
)
2004 update_auto_increment();
2007 dynstr_free(&bulk_insert
);
2009 DBUG_RETURN(my_errno
= error
);
2014 ha_federated::update_auto_increment
2016 This method ensures that last_insert_id() works properly. What it simply does
2017 is calls last_insert_id() on the foreign database immediately after insert
2018 (if the table has an auto_increment field) and sets the insert id via
2019 thd->insert_id(ID)).
2021 void ha_federated::update_auto_increment(void)
2023 THD
*thd
= current_thd
;
2024 DBUG_ENTER("ha_federated::update_auto_increment");
2026 ha_federated::info(HA_STATUS_AUTO
);
2027 thd
->first_successful_insert_id_in_cur_stmt
=
2028 stats
.auto_increment_value
;
2029 DBUG_PRINT("info",("last_insert_id: %ld", (long) stats
.auto_increment_value
));
2034 int ha_federated::optimize(THD
* thd
, HA_CHECK_OPT
* check_opt
)
2036 char query_buffer
[STRING_BUFFER_USUAL_SIZE
];
2037 String
query(query_buffer
, sizeof(query_buffer
), &my_charset_bin
);
2038 DBUG_ENTER("ha_federated::optimize");
2042 query
.set_charset(system_charset_info
);
2043 query
.append(STRING_WITH_LEN("OPTIMIZE TABLE "));
2044 append_ident(&query
, share
->table_name
, share
->table_name_length
,
2047 if (real_query(query
.ptr(), query
.length()))
2049 DBUG_RETURN(stash_remote_error());
2056 int ha_federated::repair(THD
* thd
, HA_CHECK_OPT
* check_opt
)
2058 char query_buffer
[STRING_BUFFER_USUAL_SIZE
];
2059 String
query(query_buffer
, sizeof(query_buffer
), &my_charset_bin
);
2060 DBUG_ENTER("ha_federated::repair");
2064 query
.set_charset(system_charset_info
);
2065 query
.append(STRING_WITH_LEN("REPAIR TABLE "));
2066 append_ident(&query
, share
->table_name
, share
->table_name_length
,
2068 if (check_opt
->flags
& T_QUICK
)
2069 query
.append(STRING_WITH_LEN(" QUICK"));
2070 if (check_opt
->flags
& T_EXTEND
)
2071 query
.append(STRING_WITH_LEN(" EXTENDED"));
2072 if (check_opt
->sql_flags
& TT_USEFRM
)
2073 query
.append(STRING_WITH_LEN(" USE_FRM"));
2075 if (real_query(query
.ptr(), query
.length()))
2077 DBUG_RETURN(stash_remote_error());
2085 Yes, update_row() does what you expect, it updates a row. old_data will have
2086 the previous row record in it, while new_data will have the newest data in
2089 Keep in mind that the server can do updates based on ordering if an ORDER BY
2090 clause was used. Consecutive ordering is not guaranteed.
2091 Currently new_data will not have an updated auto_increament record, or
2092 and updated timestamp field. You can do these for federated by doing these:
2093 if (table->timestamp_on_update_now)
2094 update_timestamp(new_row+table->timestamp_on_update_now-1);
2095 if (table->next_number_field && record == table->record[0])
2096 update_auto_increment();
2098 Called from sql_select.cc, sql_acl.cc, sql_update.cc, and sql_insert.cc.
2101 int ha_federated::update_row(const uchar
*old_data
, uchar
*new_data
)
2104 This used to control how the query was built. If there was a
2105 primary key, the query would be built such that there was a where
2106 clause with only that column as the condition. This is flawed,
2107 because if we have a multi-part primary key, it would only use the
2108 first part! We don't need to do this anyway, because
2109 read_range_first will retrieve the correct record, which is what
2110 is used to build the WHERE clause. We can however use this to
2111 append a LIMIT to the end if there is NOT a primary key. Why do
2112 this? Because we only are updating one record, and LIMIT enforces
2115 bool has_a_primary_key
= test(table
->s
->primary_key
!= MAX_KEY
);
2118 buffers for following strings
2120 char field_value_buffer
[STRING_BUFFER_USUAL_SIZE
];
2121 char update_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
2122 char where_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
2124 /* Work area for field values */
2125 String
field_value(field_value_buffer
, sizeof(field_value_buffer
),
2127 /* stores the update query */
2128 String
update_string(update_buffer
,
2129 sizeof(update_buffer
),
2131 /* stores the WHERE clause */
2132 String
where_string(where_buffer
,
2133 sizeof(where_buffer
),
2135 uchar
*record
= table
->record
[0];
2136 DBUG_ENTER("ha_federated::update_row");
2138 set string lengths to 0 to avoid misc chars in string
2140 field_value
.length(0);
2141 update_string
.length(0);
2142 where_string
.length(0);
2144 if (ignore_duplicates
)
2145 update_string
.append(STRING_WITH_LEN("UPDATE IGNORE "));
2147 update_string
.append(STRING_WITH_LEN("UPDATE "));
2148 append_ident(&update_string
, share
->table_name
,
2149 share
->table_name_length
, ident_quote_char
);
2150 update_string
.append(STRING_WITH_LEN(" SET "));
2153 In this loop, we want to match column names to values being inserted
2154 (while building INSERT statement).
2156 Iterate through table->field (new data) and share->old_field (old_data)
2157 using the same index to create an SQL UPDATE statement. New data is
2158 used to create SET field=value and old data is used to create WHERE
2162 for (Field
**field
= table
->field
; *field
; field
++)
2164 if (bitmap_is_set(table
->write_set
, (*field
)->field_index
))
2166 size_t field_name_length
= strlen((*field
)->field_name
);
2167 append_ident(&update_string
, (*field
)->field_name
, field_name_length
,
2169 update_string
.append(STRING_WITH_LEN(" = "));
2171 if ((*field
)->is_null())
2172 update_string
.append(STRING_WITH_LEN(" NULL "));
2176 my_bitmap_map
*old_map
= tmp_use_all_columns(table
, table
->read_set
);
2177 bool needs_quote
= (*field
)->str_needs_quotes();
2178 (*field
)->val_str(&field_value
);
2180 update_string
.append(value_quote_char
);
2181 field_value
.print(&update_string
);
2183 update_string
.append(value_quote_char
);
2184 field_value
.length(0);
2185 tmp_restore_column_map(table
->read_set
, old_map
);
2187 update_string
.append(STRING_WITH_LEN(", "));
2190 if (bitmap_is_set(table
->read_set
, (*field
)->field_index
))
2192 size_t field_name_length
= strlen((*field
)->field_name
);
2193 append_ident(&where_string
, (*field
)->field_name
, field_name_length
,
2195 if (field_in_record_is_null(table
, *field
, (char*) old_data
))
2196 where_string
.append(STRING_WITH_LEN(" IS NULL "));
2199 bool needs_quote
= (*field
)->str_needs_quotes();
2200 where_string
.append(STRING_WITH_LEN(" = "));
2201 (*field
)->val_str(&field_value
,
2202 (old_data
+ (*field
)->offset(record
)));
2204 where_string
.append(value_quote_char
);
2205 field_value
.print(&where_string
);
2207 where_string
.append(value_quote_char
);
2208 field_value
.length(0);
2210 where_string
.append(STRING_WITH_LEN(" AND "));
2214 /* Remove last ', '. This works as there must be at least on updated field */
2215 update_string
.length(update_string
.length() - sizeof_trailing_comma
);
2217 if (where_string
.length())
2219 /* chop off trailing AND */
2220 where_string
.length(where_string
.length() - sizeof_trailing_and
);
2221 update_string
.append(STRING_WITH_LEN(" WHERE "));
2222 update_string
.append(where_string
);
2226 If this table has not a primary key, then we could possibly
2227 update multiple rows. We want to make sure to only update one!
2229 if (!has_a_primary_key
)
2230 update_string
.append(STRING_WITH_LEN(" LIMIT 1"));
2232 if (real_query(update_string
.ptr(), update_string
.length()))
2234 DBUG_RETURN(stash_remote_error());
2240 This will delete a row. 'buf' will contain a copy of the row to be =deleted.
2241 The server will call this right after the current row has been called (from
2242 either a previous rnd_next() or index call).
2243 If you keep a pointer to the last row or can access a primary key it will
2244 make doing the deletion quite a bit easier.
2245 Keep in mind that the server does no guarentee consecutive deletions.
2246 ORDER BY clauses can be used.
2248 Called in sql_acl.cc and sql_udf.cc to manage internal table information.
2249 Called in sql_delete.cc, sql_insert.cc, and sql_select.cc. In sql_select
2250 it is used for removing duplicates while in insert it is used for REPLACE
2254 int ha_federated::delete_row(const uchar
*buf
)
2256 char delete_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
2257 char data_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
2258 String
delete_string(delete_buffer
, sizeof(delete_buffer
), &my_charset_bin
);
2259 String
data_string(data_buffer
, sizeof(data_buffer
), &my_charset_bin
);
2261 DBUG_ENTER("ha_federated::delete_row");
2263 delete_string
.length(0);
2264 delete_string
.append(STRING_WITH_LEN("DELETE FROM "));
2265 append_ident(&delete_string
, share
->table_name
,
2266 share
->table_name_length
, ident_quote_char
);
2267 delete_string
.append(STRING_WITH_LEN(" WHERE "));
2269 for (Field
**field
= table
->field
; *field
; field
++)
2271 Field
*cur_field
= *field
;
2273 if (bitmap_is_set(table
->read_set
, cur_field
->field_index
))
2275 append_ident(&delete_string
, (*field
)->field_name
,
2276 strlen((*field
)->field_name
), ident_quote_char
);
2277 data_string
.length(0);
2278 if (cur_field
->is_null())
2280 delete_string
.append(STRING_WITH_LEN(" IS NULL "));
2284 bool needs_quote
= cur_field
->str_needs_quotes();
2285 delete_string
.append(STRING_WITH_LEN(" = "));
2286 cur_field
->val_str(&data_string
);
2288 delete_string
.append(value_quote_char
);
2289 data_string
.print(&delete_string
);
2291 delete_string
.append(value_quote_char
);
2293 delete_string
.append(STRING_WITH_LEN(" AND "));
2297 // Remove trailing AND
2298 delete_string
.length(delete_string
.length() - sizeof_trailing_and
);
2300 delete_string
.length(delete_string
.length() - sizeof_trailing_where
);
2302 delete_string
.append(STRING_WITH_LEN(" LIMIT 1"));
2304 ("Delete sql: %s", delete_string
.c_ptr_quick()));
2305 if (real_query(delete_string
.ptr(), delete_string
.length()))
2307 DBUG_RETURN(stash_remote_error());
2309 stats
.deleted
+= (ha_rows
) mysql
->affected_rows
;
2310 stats
.records
-= (ha_rows
) mysql
->affected_rows
;
2312 ("rows deleted %ld rows deleted for all time %ld",
2313 (long) mysql
->affected_rows
, (long) stats
.deleted
));
2320 Positions an index cursor to the index specified in the handle. Fetches the
2321 row if available. If the key value is null, begin at the first key of the
2322 index. This method, which is called in the case of an SQL statement having
2323 a WHERE clause on a non-primary key index, simply calls index_read_idx.
2326 int ha_federated::index_read(uchar
*buf
, const uchar
*key
,
2327 uint key_len
, ha_rkey_function find_flag
)
2329 DBUG_ENTER("ha_federated::index_read");
2332 DBUG_RETURN(index_read_idx_with_result_set(buf
, active_index
, key
,
2339 Positions an index cursor to the index specified in key. Fetches the
2340 row if any. This is only used to read whole keys.
2342 This method is called via index_read in the case of a WHERE clause using
2343 a primary key index OR is called DIRECTLY when the WHERE clause
2344 uses a PRIMARY KEY index.
2347 This uses an internal result set that is deleted before function
2348 returns. We need to be able to be calable from ha_rnd_pos()
2351 int ha_federated::index_read_idx(uchar
*buf
, uint index
, const uchar
*key
,
2352 uint key_len
, enum ha_rkey_function find_flag
)
2355 MYSQL_RES
*mysql_result
;
2356 DBUG_ENTER("ha_federated::index_read_idx");
2358 if ((retval
= index_read_idx_with_result_set(buf
, index
, key
,
2361 DBUG_RETURN(retval
);
2362 mysql_free_result(mysql_result
);
2369 Create result set for rows matching query and return first row
2372 0 ok In this case *result will contain the result set
2374 # error In this case *result will contain 0
2375 table->status == STATUS_NOT_FOUND
2378 int ha_federated::index_read_idx_with_result_set(uchar
*buf
, uint index
,
2381 ha_rkey_function find_flag
,
2385 char error_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
2386 char index_value
[STRING_BUFFER_USUAL_SIZE
];
2387 char sql_query_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
2388 String
index_string(index_value
,
2389 sizeof(index_value
),
2391 String
sql_query(sql_query_buffer
,
2392 sizeof(sql_query_buffer
),
2395 DBUG_ENTER("ha_federated::index_read_idx_with_result_set");
2397 *result
= 0; // In case of errors
2398 index_string
.length(0);
2399 sql_query
.length(0);
2400 ha_statistic_increment(&SSV::ha_read_key_count
);
2402 sql_query
.append(share
->select_query
);
2405 range
.length
= key_len
;
2406 range
.flag
= find_flag
;
2407 create_where_from_key(&index_string
,
2408 &table
->key_info
[index
],
2411 sql_query
.append(index_string
);
2413 if (real_query(sql_query
.ptr(), sql_query
.length()))
2415 sprintf(error_buffer
, "error: %d '%s'",
2416 mysql_errno(mysql
), mysql_error(mysql
));
2417 retval
= ER_QUERY_ON_FOREIGN_DATA_SOURCE
;
2420 if (!(*result
= store_result(mysql
)))
2422 retval
= HA_ERR_END_OF_FILE
;
2425 if ((retval
= read_next(buf
, *result
)))
2427 mysql_free_result(*result
);
2430 table
->status
= STATUS_NOT_FOUND
;
2431 DBUG_RETURN(retval
);
2436 table
->status
= STATUS_NOT_FOUND
;
2437 my_error(retval
, MYF(0), error_buffer
);
2438 DBUG_RETURN(retval
);
2443 This method is used exlusevely by filesort() to check if we
2444 can create sorting buffers of necessary size.
2445 If the handler returns more records that it declares
2446 here server can just crash on filesort().
2447 We cannot guarantee that's not going to happen with
2448 the FEDERATED engine, as we have records==0 always if the
2449 client is a VIEW, and for the table the number of
2450 records can inpredictably change during execution.
2451 So we return maximum possible value here.
2454 ha_rows
ha_federated::estimate_rows_upper_bound()
2456 return HA_POS_ERROR
;
2460 /* Initialized at each key walk (called multiple times unlike rnd_init()) */
2462 int ha_federated::index_init(uint keynr
, bool sorted
)
2464 DBUG_ENTER("ha_federated::index_init");
2465 DBUG_PRINT("info", ("table: '%s' key: %u", table
->s
->table_name
.str
, keynr
));
2466 active_index
= keynr
;
2475 int ha_federated::read_range_first(const key_range
*start_key
,
2476 const key_range
*end_key
,
2477 bool eq_range_arg
, bool sorted
)
2479 char sql_query_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
2481 String
sql_query(sql_query_buffer
,
2482 sizeof(sql_query_buffer
),
2484 DBUG_ENTER("ha_federated::read_range_first");
2486 DBUG_ASSERT(!(start_key
== NULL
&& end_key
== NULL
));
2488 sql_query
.length(0);
2489 sql_query
.append(share
->select_query
);
2490 create_where_from_key(&sql_query
,
2491 &table
->key_info
[active_index
],
2492 start_key
, end_key
, 0, eq_range_arg
);
2493 if (real_query(sql_query
.ptr(), sql_query
.length()))
2495 retval
= ER_QUERY_ON_FOREIGN_DATA_SOURCE
;
2498 sql_query
.length(0);
2500 if (!(stored_result
= store_result(mysql
)))
2502 retval
= HA_ERR_END_OF_FILE
;
2506 DBUG_RETURN(read_next(table
->record
[0], stored_result
));
2509 table
->status
= STATUS_NOT_FOUND
;
2510 DBUG_RETURN(retval
);
2514 int ha_federated::read_range_next()
2516 DBUG_ENTER("ha_federated::read_range_next");
2517 DBUG_RETURN(rnd_next(table
->record
[0]));
2521 /* Used to read forward through the index. */
2522 int ha_federated::index_next(uchar
*buf
)
2524 DBUG_ENTER("ha_federated::index_next");
2525 ha_statistic_increment(&SSV::ha_read_next_count
);
2526 DBUG_RETURN(read_next(buf
, stored_result
));
2531 rnd_init() is called when the system wants the storage engine to do a table
2534 This is the method that gets data for the SELECT calls.
2536 See the federated in the introduction at the top of this file to see when
2537 rnd_init() is called.
2539 Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc,
2540 sql_table.cc, and sql_update.cc.
2543 int ha_federated::rnd_init(bool scan
)
2545 DBUG_ENTER("ha_federated::rnd_init");
2547 The use of the 'scan' flag is incredibly important for this handler
2548 to work properly, especially with updates containing WHERE clauses
2549 using indexed columns.
2551 When the initial query contains a WHERE clause of the query using an
2552 indexed column, it's index_read_idx that selects the exact record from
2553 the foreign database.
2555 When there is NO index in the query, either due to not having a WHERE
2556 clause, or the WHERE clause is using columns that are not indexed, a
2557 'full table scan' done by rnd_init, which in this situation simply means
2558 a 'select * from ...' on the foreign table.
2560 In other words, this 'scan' flag gives us the means to ensure that if
2561 there is an index involved in the query, we want index_read_idx to
2562 retrieve the exact record (scan flag is 0), and do not want rnd_init
2563 to do a 'full table scan' and wipe out that result set.
2565 Prior to using this flag, the problem was most apparent with updates.
2567 An initial query like 'UPDATE tablename SET anything = whatever WHERE
2568 indexedcol = someval', index_read_idx would get called, using a query
2569 constructed with a WHERE clause built from the values of index ('indexcol'
2570 in this case, having a value of 'someval'). mysql_store_result would
2571 then get called (this would be the result set we want to use).
2573 After this rnd_init (from sql_update.cc) would be called, it would then
2574 unecessarily call "select * from table" on the foreign table, then call
2575 mysql_store_result, which would wipe out the correct previous result set
2576 from the previous call of index_read_idx's that had the result set
2577 containing the correct record, hence update the wrong row!
2583 if (real_query(share
->select_query
, strlen(share
->select_query
)) ||
2584 !(stored_result
= store_result(mysql
)))
2585 DBUG_RETURN(stash_remote_error());
2591 int ha_federated::rnd_end()
2593 DBUG_ENTER("ha_federated::rnd_end");
2594 DBUG_RETURN(index_end());
2598 int ha_federated::index_end(void)
2600 DBUG_ENTER("ha_federated::index_end");
2602 active_index
= MAX_KEY
;
2608 This is called for each row of the table scan. When you run out of records
2609 you should return HA_ERR_END_OF_FILE. Fill buff up with the row information.
2610 The Field structure for the table is the key to getting data into buf
2611 in a manner that will allow the server to understand it.
2613 Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc,
2614 sql_table.cc, and sql_update.cc.
2617 int ha_federated::rnd_next(uchar
*buf
)
2619 DBUG_ENTER("ha_federated::rnd_next");
2621 if (stored_result
== 0)
2624 Return value of rnd_init is not always checked (see records.cc),
2625 so we can get here _even_ if there is _no_ pre-fetched result-set!
2626 TODO: fix it. We can delete this in 5.1 when rnd_init() is checked.
2630 DBUG_RETURN(read_next(buf
, stored_result
));
2635 ha_federated::read_next
2637 reads from a result set and converts to mysql internal
2641 field_in_record_is_null()
2642 buf byte pointer to record
2643 result mysql result set
2646 This method is a wrapper method that reads one record from a result
2647 set and converts it to the internal table format
2654 int ha_federated::read_next(uchar
*buf
, MYSQL_RES
*result
)
2658 DBUG_ENTER("ha_federated::read_next");
2660 table
->status
= STATUS_NOT_FOUND
; // For easier return
2662 /* Save current data cursor position. */
2663 current_position
= result
->data_cursor
;
2665 /* Fetch a row, insert it back in a row format. */
2666 if (!(row
= mysql_fetch_row(result
)))
2667 DBUG_RETURN(HA_ERR_END_OF_FILE
);
2669 if (!(retval
= convert_row_to_internal_format(buf
, row
, result
)))
2672 DBUG_RETURN(retval
);
2677 @brief Store a reference to current row.
2679 @details During a query execution we may have different result sets (RS),
2680 e.g. for different ranges. All the RS's used are stored in
2681 memory and placed in @c results dynamic array. At the end of
2682 execution all stored RS's are freed at once in the
2683 @c ha_federated::reset().
2684 So, in case of federated, a reference to current row is a
2685 stored result address and current data cursor position.
2686 As we keep all RS in memory during a query execution,
2687 we can get any record using the reference any time until
2688 @c ha_federated::reset() is called.
2689 TODO: we don't have to store all RS's rows but only those
2690 we call @c ha_federated::position() for, so we can free memory
2691 where we store other rows in the @c ha_federated::index_end().
2693 @param[in] record record data (unused)
2696 void ha_federated::position(const uchar
*record
__attribute__ ((unused
)))
2698 DBUG_ENTER("ha_federated::position");
2700 DBUG_ASSERT(stored_result
);
2702 position_called
= TRUE
;
2703 /* Store result set address. */
2704 memcpy_fixed(ref
, &stored_result
, sizeof(MYSQL_RES
*));
2705 /* Store data cursor position. */
2706 memcpy_fixed(ref
+ sizeof(MYSQL_RES
*), ¤t_position
,
2707 sizeof(MYSQL_ROW_OFFSET
));
2713 This is like rnd_next, but you are given a position to use to determine the
2714 row. The position will be of the type that you stored in ref.
2716 This method is required for an ORDER BY
2718 Called from filesort.cc records.cc sql_insert.cc sql_select.cc sql_update.cc.
2721 int ha_federated::rnd_pos(uchar
*buf
, uchar
*pos
)
2724 DBUG_ENTER("ha_federated::rnd_pos");
2726 ha_statistic_increment(&SSV::ha_read_rnd_count
);
2728 /* Get stored result set. */
2729 memcpy_fixed(&result
, pos
, sizeof(MYSQL_RES
*));
2730 DBUG_ASSERT(result
);
2731 /* Set data cursor position. */
2732 memcpy_fixed(&result
->data_cursor
, pos
+ sizeof(MYSQL_RES
*),
2733 sizeof(MYSQL_ROW_OFFSET
));
2735 DBUG_RETURN(read_next(buf
, result
));
2740 ::info() is used to return information to the optimizer.
2741 Currently this table handler doesn't implement most of the fields
2742 really needed. SHOW also makes use of this data
2743 Another note, you will probably want to have the following in your
2747 The reason is that the server will optimize for cases of only a single
2748 record. If in a table scan you don't know the number of records
2749 it will probably be better to set records to two so you can return
2750 as many records as you need.
2751 Along with records a few more variables you may wish to set are:
2758 Take a look at the public variables in handler.h for more information.
2783 int ha_federated::info(uint flag
)
2785 char status_buf
[FEDERATED_QUERY_BUFFER_SIZE
];
2788 MYSQL_RES
*result
= 0;
2790 String
status_query_string(status_buf
, sizeof(status_buf
), &my_charset_bin
);
2791 DBUG_ENTER("ha_federated::info");
2793 error_code
= ER_QUERY_ON_FOREIGN_DATA_SOURCE
;
2794 /* we want not to show table status if not needed to do so */
2795 if (flag
& (HA_STATUS_VARIABLE
| HA_STATUS_CONST
))
2797 status_query_string
.length(0);
2798 status_query_string
.append(STRING_WITH_LEN("SHOW TABLE STATUS LIKE "));
2799 append_ident(&status_query_string
, share
->table_name
,
2800 share
->table_name_length
, value_quote_char
);
2802 if (real_query(status_query_string
.ptr(), status_query_string
.length()))
2805 status_query_string
.length(0);
2807 result
= mysql_store_result(mysql
);
2810 We're going to use fields num. 4, 12 and 13 of the resultset,
2811 so make sure we have these fields.
2813 if (!result
|| (mysql_num_fields(result
) < 14))
2816 if (!mysql_num_rows(result
))
2819 if (!(row
= mysql_fetch_row(result
)))
2823 deleted is set in ha_federated::info
2826 need to figure out what this means as far as federated is concerned,
2827 since we don't have a "file"
2829 data_file_length = ?
2830 index_file_length = ?
2834 stats
.records
= (ha_rows
) my_strtoll10(row
[4], (char**) 0,
2837 stats
.mean_rec_length
= (ulong
) my_strtoll10(row
[5], (char**) 0, &error
);
2839 stats
.data_file_length
= stats
.records
* stats
.mean_rec_length
;
2841 if (row
[12] != NULL
)
2842 stats
.update_time
= (ulong
) my_strtoll10(row
[12], (char**) 0,
2844 if (row
[13] != NULL
)
2845 stats
.check_time
= (ulong
) my_strtoll10(row
[13], (char**) 0,
2849 size of IO operations (This is based on a good guess, no high science
2852 if (flag
& HA_STATUS_CONST
)
2853 stats
.block_size
= 4096;
2857 if (flag
& HA_STATUS_AUTO
)
2858 stats
.auto_increment_value
= mysql
->last_used_con
->insert_id
;
2860 mysql_free_result(result
);
2865 mysql_free_result(result
);
2868 my_printf_error(error_code
, ": %d : %s", MYF(0),
2869 mysql_errno(mysql
), mysql_error(mysql
));
2872 if (remote_error_number
!= -1 /* error already reported */)
2874 error_code
= remote_error_number
;
2875 my_error(error_code
, MYF(0), ER(error_code
));
2877 DBUG_RETURN(error_code
);
2882 @brief Handles extra signals from MySQL server
2884 @param[in] operation Hint for storage engine
2886 @return Operation Status
2889 int ha_federated::extra(ha_extra_function operation
)
2891 DBUG_ENTER("ha_federated::extra");
2892 switch (operation
) {
2893 case HA_EXTRA_IGNORE_DUP_KEY
:
2894 ignore_duplicates
= TRUE
;
2896 case HA_EXTRA_NO_IGNORE_DUP_KEY
:
2897 insert_dup_update
= FALSE
;
2898 ignore_duplicates
= FALSE
;
2900 case HA_EXTRA_WRITE_CAN_REPLACE
:
2901 replace_duplicates
= TRUE
;
2903 case HA_EXTRA_WRITE_CANNOT_REPLACE
:
2905 We use this flag to ensure that we do not create an "INSERT IGNORE"
2906 statement when inserting new rows into the remote table.
2908 replace_duplicates
= FALSE
;
2910 case HA_EXTRA_INSERT_WITH_UPDATE
:
2911 insert_dup_update
= TRUE
;
2915 DBUG_PRINT("info",("unhandled operation: %d", (uint
) operation
));
2922 @brief Reset state of file to after 'open'.
2924 @detail This function is called after every statement for all tables
2925 used by that statement.
2927 @return Operation status
2931 int ha_federated::reset(void)
2933 insert_dup_update
= FALSE
;
2934 ignore_duplicates
= FALSE
;
2935 replace_duplicates
= FALSE
;
2937 /* Free stored result sets. */
2938 for (uint i
= 0; i
< results
.elements
; i
++)
2941 get_dynamic(&results
, (uchar
*) &result
, i
);
2942 mysql_free_result(result
);
2944 reset_dynamic(&results
);
2951 Used to delete all rows in a table. Both for cases of truncate and
2952 for cases where the optimizer realizes that all rows will be
2953 removed as a result of a SQL statement.
2955 Called from item_sum.cc by Item_func_group_concat::clear(),
2956 Item_sum_count_distinct::clear(), and Item_func_group_concat::clear().
2957 Called from sql_delete.cc by mysql_delete().
2958 Called from sql_select.cc by JOIN::reinit().
2959 Called from sql_union.cc by st_select_lex_unit::exec().
2962 int ha_federated::delete_all_rows()
2964 char query_buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
2965 String
query(query_buffer
, sizeof(query_buffer
), &my_charset_bin
);
2966 DBUG_ENTER("ha_federated::delete_all_rows");
2970 query
.set_charset(system_charset_info
);
2971 query
.append(STRING_WITH_LEN("TRUNCATE "));
2972 append_ident(&query
, share
->table_name
, share
->table_name_length
,
2976 TRUNCATE won't return anything in mysql_affected_rows
2978 if (real_query(query
.ptr(), query
.length()))
2980 DBUG_RETURN(stash_remote_error());
2982 stats
.deleted
+= stats
.records
;
2989 The idea with handler::store_lock() is the following:
2991 The statement decided which locks we should need for the table
2992 for updates/deletes/inserts we get WRITE locks, for SELECT... we get
2995 Before adding the lock into the table lock handler (see thr_lock.c)
2996 mysqld calls store lock with the requested locks. Store lock can now
2997 modify a write lock to a read lock (or some other lock), ignore the
2998 lock (if we don't want to use MySQL table locks at all) or add locks
2999 for many tables (like we do when we are using a MERGE handler).
3001 Berkeley DB for federated changes all WRITE locks to TL_WRITE_ALLOW_WRITE
3002 (which signals that we are doing WRITES, but we are still allowing other
3003 reader's and writer's.
3005 When releasing locks, store_lock() are also called. In this case one
3006 usually doesn't have to do anything.
3008 In some exceptional cases MySQL may send a request for a TL_IGNORE;
3009 This means that we are requesting the same lock as last time and this
3010 should also be ignored. (This may happen when someone does a flush
3011 table when we have opened a part of the tables, in which case mysqld
3012 closes and reopens the tables and tries to get the same locks at last
3013 time). In the future we will probably try to remove this.
3015 Called from lock.cc by get_lock_data().
3018 THR_LOCK_DATA
**ha_federated::store_lock(THD
*thd
,
3020 enum thr_lock_type lock_type
)
3022 DBUG_ENTER("ha_federated::store_lock");
3023 if (lock_type
!= TL_IGNORE
&& lock
.type
== TL_UNLOCK
)
3026 Here is where we get into the guts of a row level lock.
3028 If we are not doing a LOCK TABLE or DISCARD/IMPORT
3029 TABLESPACE, then allow multiple writers
3032 if ((lock_type
>= TL_WRITE_CONCURRENT_INSERT
&&
3033 lock_type
<= TL_WRITE
) && !thd
->in_lock_tables
)
3034 lock_type
= TL_WRITE_ALLOW_WRITE
;
3037 In queries of type INSERT INTO t1 SELECT ... FROM t2 ...
3038 MySQL would use the lock TL_READ_NO_INSERT on t2, and that
3039 would conflict with TL_WRITE_ALLOW_WRITE, blocking all inserts
3040 to t2. Convert the lock to a normal read lock to allow
3041 concurrent inserts to t2.
3044 if (lock_type
== TL_READ_NO_INSERT
&& !thd
->in_lock_tables
)
3047 lock
.type
= lock_type
;
3056 create() does nothing, since we have no local setup of our own.
3057 FUTURE: We should potentially connect to the foreign database and
3060 int ha_federated::create(const char *name
, TABLE
*table_arg
,
3061 HA_CREATE_INFO
*create_info
)
3064 THD
*thd
= current_thd
;
3065 FEDERATED_SHARE tmp_share
; // Only a temporary share, to test the url
3066 DBUG_ENTER("ha_federated::create");
3068 retval
= parse_url(thd
->mem_root
, &tmp_share
, table_arg
, 1);
3070 DBUG_RETURN(retval
);
3075 int ha_federated::real_connect()
3077 char buffer
[FEDERATED_QUERY_BUFFER_SIZE
];
3078 String
sql_query(buffer
, sizeof(buffer
), &my_charset_bin
);
3079 DBUG_ENTER("ha_federated::real_connect");
3083 Ensure that we do not hold the LOCK_open mutex while attempting
3084 to establish Federated connection to guard against a trivial
3085 Denial of Service scenerio.
3087 safe_mutex_assert_not_owner(&LOCK_open
);
3089 DBUG_ASSERT(mysql
== NULL
);
3091 if (!(mysql
= mysql_init(NULL
)))
3093 remote_error_number
= HA_ERR_OUT_OF_MEM
;
3098 BUG# 17044 Federated Storage Engine is not UTF8 clean
3099 Add set names to whatever charset the table is at open
3102 /* this sets the csname like 'set names utf8' */
3103 mysql_options(mysql
,MYSQL_SET_CHARSET_NAME
,
3104 this->table
->s
->table_charset
->csname
);
3106 sql_query
.length(0);
3108 if (!mysql_real_connect(mysql
,
3116 stash_remote_error();
3119 my_error(ER_CONNECT_TO_FOREIGN_DATA_SOURCE
, MYF(0), remote_error_buf
);
3120 remote_error_number
= -1;
3125 We have established a connection, lets try a simple dummy query just
3126 to check that the table and expected columns are present.
3128 sql_query
.append(share
->select_query
);
3129 sql_query
.append(STRING_WITH_LEN(" WHERE 1=0"));
3130 if (mysql_real_query(mysql
, sql_query
.ptr(), sql_query
.length()))
3132 sql_query
.length(0);
3133 sql_query
.append("error: ");
3134 sql_query
.qs_append(mysql_errno(mysql
));
3135 sql_query
.append(" '");
3136 sql_query
.append(mysql_error(mysql
));
3137 sql_query
.append("'");
3140 my_error(ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST
, MYF(0), sql_query
.ptr());
3141 remote_error_number
= -1;
3145 /* Just throw away the result, no rows anyways but need to keep in sync */
3146 mysql_free_result(mysql_store_result(mysql
));
3149 Since we do not support transactions at this version, we can let the client
3150 API silently reconnect. For future versions, we will need more logic to
3151 deal with transactions
3154 mysql
->reconnect
= 1;
3159 int ha_federated::real_query(const char *query
, size_t length
)
3162 DBUG_ENTER("ha_federated::real_query");
3164 if (!mysql
&& (rc
= real_connect()))
3167 if (!query
|| !length
)
3170 rc
= mysql_real_query(mysql
, query
, (uint
) length
);
3177 int ha_federated::stash_remote_error()
3179 DBUG_ENTER("ha_federated::stash_remote_error()");
3181 DBUG_RETURN(remote_error_number
);
3182 remote_error_number
= mysql_errno(mysql
);
3183 strmake(remote_error_buf
, mysql_error(mysql
), sizeof(remote_error_buf
)-1);
3184 if (remote_error_number
== ER_DUP_ENTRY
||
3185 remote_error_number
== ER_DUP_KEY
)
3186 DBUG_RETURN(HA_ERR_FOUND_DUPP_KEY
);
3187 DBUG_RETURN(HA_FEDERATED_ERROR_WITH_REMOTE_SYSTEM
);
3191 bool ha_federated::get_error_message(int error
, String
* buf
)
3193 DBUG_ENTER("ha_federated::get_error_message");
3194 DBUG_PRINT("enter", ("error: %d", error
));
3195 if (error
== HA_FEDERATED_ERROR_WITH_REMOTE_SYSTEM
)
3197 buf
->append(STRING_WITH_LEN("Error on remote system: "));
3198 buf
->qs_append(remote_error_number
);
3199 buf
->append(STRING_WITH_LEN(": "));
3200 buf
->append(remote_error_buf
);
3202 remote_error_number
= 0;
3203 remote_error_buf
[0]= '\0';
3205 DBUG_PRINT("exit", ("message: %s", buf
->ptr()));
3211 @brief Store a result set.
3213 @details Call @c mysql_store_result() to save a result set then
3214 append it to the stored results array.
3216 @param[in] mysql_arg MySLQ connection structure.
3218 @return Stored result set (MYSQL_RES object).
3221 MYSQL_RES
*ha_federated::store_result(MYSQL
*mysql_arg
)
3223 MYSQL_RES
*result
= mysql_store_result(mysql_arg
);
3224 DBUG_ENTER("ha_federated::store_result");
3227 (void) insert_dynamic(&results
, (uchar
*) &result
);
3229 position_called
= FALSE
;
3230 DBUG_RETURN(result
);
3234 void ha_federated::free_result()
3236 DBUG_ENTER("ha_federated::free_result");
3237 if (stored_result
&& !position_called
)
3239 mysql_free_result(stored_result
);
3241 if (results
.elements
> 0)
3248 int ha_federated::external_lock(THD
*thd
, int lock_type
)
3251 DBUG_ENTER("ha_federated::external_lock");
3254 Support for transactions disabled until WL#2952 fixes it.
3256 #ifdef XXX_SUPERCEDED_BY_WL2952
3257 if (lock_type
!= F_UNLCK
)
3259 ha_federated
*trx
= (ha_federated
*)thd_get_ha_data(thd
, ht
);
3261 DBUG_PRINT("info",("federated not lock F_UNLCK"));
3262 if (!(thd
->options
& (OPTION_NOT_AUTOCOMMIT
| OPTION_BEGIN
)))
3264 DBUG_PRINT("info",("federated autocommit"));
3266 This means we are doing an autocommit
3268 error
= connection_autocommit(TRUE
);
3271 DBUG_PRINT("info", ("error setting autocommit TRUE: %d", error
));
3274 trans_register_ha(thd
, FALSE
, ht
);
3278 DBUG_PRINT("info",("not autocommit"));
3282 This is where a transaction gets its start
3284 error
= connection_autocommit(FALSE
);
3287 DBUG_PRINT("info", ("error setting autocommit FALSE: %d", error
));
3290 thd_set_ha_data(thd
, ht
, this);
3291 trans_register_ha(thd
, TRUE
, ht
);
3293 Send a lock table to the remote end.
3294 We do not support this at the moment
3296 if (thd
->options
& (OPTION_TABLE_LOCK
))
3298 DBUG_PRINT("info", ("We do not support lock table yet"));
3304 for (ptr
= trx
; ptr
; ptr
= ptr
->trx_next
)
3307 else if (!ptr
->trx_next
)
3308 ptr
->trx_next
= this;
3312 #endif /* XXX_SUPERCEDED_BY_WL2952 */
3317 static int federated_commit(handlerton
*hton
, THD
*thd
, bool all
)
3320 ha_federated
*trx
= (ha_federated
*) thd_get_ha_data(thd
, hton
);
3321 DBUG_ENTER("federated_commit");
3326 ha_federated
*ptr
, *old
= NULL
;
3327 for (ptr
= trx
; ptr
; old
= ptr
, ptr
= ptr
->trx_next
)
3330 old
->trx_next
= NULL
;
3331 error
= ptr
->connection_commit();
3332 if (error
&& !return_val
)
3335 thd_set_ha_data(thd
, hton
, NULL
);
3338 DBUG_PRINT("info", ("error val: %d", return_val
));
3339 DBUG_RETURN(return_val
);
3343 static int federated_rollback(handlerton
*hton
, THD
*thd
, bool all
)
3346 ha_federated
*trx
= (ha_federated
*)thd_get_ha_data(thd
, hton
);
3347 DBUG_ENTER("federated_rollback");
3352 ha_federated
*ptr
, *old
= NULL
;
3353 for (ptr
= trx
; ptr
; old
= ptr
, ptr
= ptr
->trx_next
)
3356 old
->trx_next
= NULL
;
3357 error
= ptr
->connection_rollback();
3358 if (error
&& !return_val
)
3361 thd_set_ha_data(thd
, hton
, NULL
);
3364 DBUG_PRINT("info", ("error val: %d", return_val
));
3365 DBUG_RETURN(return_val
);
3368 int ha_federated::connection_commit()
3370 DBUG_ENTER("ha_federated::connection_commit");
3371 DBUG_RETURN(execute_simple_query("COMMIT", 6));
3375 int ha_federated::connection_rollback()
3377 DBUG_ENTER("ha_federated::connection_rollback");
3378 DBUG_RETURN(execute_simple_query("ROLLBACK", 8));
3382 int ha_federated::connection_autocommit(bool state
)
3385 DBUG_ENTER("ha_federated::connection_autocommit");
3386 text
= (state
== TRUE
) ? "SET AUTOCOMMIT=1" : "SET AUTOCOMMIT=0";
3387 DBUG_RETURN(execute_simple_query(text
, 16));
3391 int ha_federated::execute_simple_query(const char *query
, int len
)
3393 DBUG_ENTER("ha_federated::execute_simple_query");
3395 if (mysql_real_query(mysql
, query
, len
))
3397 DBUG_RETURN(stash_remote_error());
3402 struct st_mysql_storage_engine federated_storage_engine
=
3403 { MYSQL_HANDLERTON_INTERFACE_VERSION
};
3405 mysql_declare_plugin(federated
)
3407 MYSQL_STORAGE_ENGINE_PLUGIN
,
3408 &federated_storage_engine
,
3410 "Patrick Galbraith and Brian Aker, MySQL AB",
3411 "Federated MySQL storage engine",
3413 federated_db_init
, /* Plugin Init */
3414 federated_done
, /* Plugin Deinit */
3416 NULL
, /* status variables */
3417 NULL
, /* system variables */
3418 NULL
/* config options */
3420 mysql_declare_plugin_end
;