From: Mattias Jonsson Date: May 8 2011 9:23pm Subject: bzr commit into mysql-5.1 branch (mattias.jonsson:3682) Bug#53775 Bug#11761296 List-Archive: http://lists.mysql.com/commits/136884 X-Bug: 53775,11761296 Message-Id: <201105082123.p48LNBOE023810@acsmt358.oracle.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============1327072748==" --===============1327072748== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///C:/ade/mysql-bzr/b11761296-5.1/ based on revid:alexander.nozdrin@stripped 3682 Mattias Jonsson 2011-05-08 Bug#11761296 bug#53775: QUERY ON PARTITIONED TABLE RETURNS CACHED RESULT FROM PREVIOUS TRANSACTION Partitioned tables used the default query cache implementations which always allowed queries to be cached and never used a callback to verify if the query still was valid. Fixed by implementing the register_query_cache_table function and a matching callback in ha_partition based on the following: 1) from handler.h: generic (but static) call back function i.e. all tables/partitions will have the same callback function for the same engine. 2) The only two engines that have own register_query_cache_table functions and are supported by ha_partition (MyISAM and InnoDB) does both always set engine_data to 0. Due to 1) the underlying engines callback can be registered on the TABLE_SHARE::ha_data which can be used by a new ha_partition callback function. Due to 2) a partitioned table can use the engine_data to store which partitions that were used in the query and that way optimize the callbacks to only callback the partitions used by the original query (with the limitation of 64 bit storage). removed: mysql-test/t/cache_innodb-master.opt added: mysql-test/r/cache_myisam.result mysql-test/r/partition_cache_innodb.result mysql-test/t/cache_myisam.test mysql-test/t/partition_cache_innodb.test modified: mysql-test/include/query_cache.inc mysql-test/r/cache_innodb.result sql/ha_partition.cc sql/ha_partition.h sql/sql_base.cc === modified file 'mysql-test/include/query_cache.inc' --- a/mysql-test/include/query_cache.inc 2009-05-15 10:15:56 +0000 +++ b/mysql-test/include/query_cache.inc 2011-05-08 21:23:01 +0000 @@ -4,6 +4,9 @@ # $engine_type -- storage engine to be tested # $test_foreign_keys -- 0, skip foreign key tests # -- 1, do not skip foreign key tests +# $partitions_a -- partition clause for tables with column 'a' +# $partitions_id -- partition clause for tables with column 'id' +# $partitions_s1 -- partition clause for tables with column 's1' # have to be set before sourcing this script. # # Last update: @@ -12,6 +15,8 @@ # main code went into include/query_cache.inc # +let $save_query_cache_size=`select @@global.query_cache_size`; +SET GLOBAL query_cache_size = 1024 * 1024; eval SET SESSION STORAGE_ENGINE = $engine_type; # Initialise @@ -24,7 +29,7 @@ drop table if exists t1,t2,t3; # flush status; set autocommit=0; -create table t1 (a int not null); +eval create table t1 (a int not null)$partitions_a; insert into t1 values (1),(2),(3); select * from t1; show status like "Qcache_queries_in_cache"; @@ -32,15 +37,15 @@ drop table t1; commit; set autocommit=1; begin; -create table t1 (a int not null); +eval create table t1 (a int not null)$partitions_a; insert into t1 values (1),(2),(3); select * from t1; show status like "Qcache_queries_in_cache"; drop table t1; commit; -create table t1 (a int not null); -create table t2 (a int not null); -create table t3 (a int not null); +eval create table t1 (a int not null)$partitions_a; +eval create table t2 (a int not null)$partitions_a; +eval create table t3 (a int not null)$partitions_a; insert into t1 values (1),(2); insert into t2 values (1),(2); insert into t3 values (1),(2); @@ -67,7 +72,7 @@ commit; show status like "Qcache_queries_in_cache"; drop table t3,t2,t1; -CREATE TABLE t1 (id int(11) NOT NULL auto_increment, PRIMARY KEY (id)); +eval CREATE TABLE t1 (id int(11) NOT NULL auto_increment, PRIMARY KEY (id))$partitions_id; select count(*) from t1; insert into t1 (id) values (0); select count(*) from t1; @@ -78,7 +83,6 @@ if ($test_foreign_keys) # # one statement roll back inside transation # -let $save_query_cache_size=`select @@global.query_cache_size`; set GLOBAL query_cache_size=1355776; CREATE TABLE t1 ( id int(10) NOT NULL auto_increment, a varchar(25) default NULL, PRIMARY KEY (id), UNIQUE KEY a (a)); CREATE TABLE t2 ( id int(10) NOT NULL auto_increment, b varchar(25) default NULL, PRIMARY KEY (id), UNIQUE KEY b (b)); @@ -118,7 +122,7 @@ SET GLOBAL query_cache_size = 200000; flush status; SET @@autocommit=1; eval SET SESSION STORAGE_ENGINE = $engine_type; -CREATE TABLE t2 (s1 int, s2 varchar(1000), key(s1)); +eval CREATE TABLE t2 (s1 int, s2 varchar(1000), key(s1))$partitions_s1; INSERT INTO t2 VALUES (1,repeat('a',10)),(2,repeat('a',10)),(3,repeat('a',10)),(4,repeat('a',10)); COMMIT; START TRANSACTION; === modified file 'mysql-test/r/cache_innodb.result' --- a/mysql-test/r/cache_innodb.result 2009-01-31 17:55:06 +0000 +++ b/mysql-test/r/cache_innodb.result 2011-05-08 21:23:01 +0000 @@ -1,3 +1,4 @@ +SET GLOBAL query_cache_size = 1024 * 1024; SET SESSION STORAGE_ENGINE = InnoDB; drop table if exists t1,t2,t3; flush status; @@ -218,5 +219,5 @@ Qcache_queries_in_cache 1 show status like "Qcache_hits"; Variable_name Value Qcache_hits 1 -set GLOBAL query_cache_size=1048576; +set GLOBAL query_cache_size=0; drop table t2; === added file 'mysql-test/r/cache_myisam.result' --- a/mysql-test/r/cache_myisam.result 1970-01-01 00:00:00 +0000 +++ b/mysql-test/r/cache_myisam.result 2011-05-08 21:23:01 +0000 @@ -0,0 +1,204 @@ +SET GLOBAL query_cache_size = 1024 * 1024; +SET SESSION STORAGE_ENGINE = MyISAM; +drop table if exists t1,t2,t3; +flush status; +set autocommit=0; +create table t1 (a int not null); +insert into t1 values (1),(2),(3); +select * from t1; +a +1 +2 +3 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 1 +drop table t1; +commit; +set autocommit=1; +begin; +create table t1 (a int not null); +insert into t1 values (1),(2),(3); +select * from t1; +a +1 +2 +3 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 1 +drop table t1; +commit; +create table t1 (a int not null); +create table t2 (a int not null); +create table t3 (a int not null); +insert into t1 values (1),(2); +insert into t2 values (1),(2); +insert into t3 values (1),(2); +select * from t1; +a +1 +2 +select * from t2; +a +1 +2 +select * from t3; +a +1 +2 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 3 +show status like "Qcache_hits"; +Variable_name Value +Qcache_hits 0 +begin; +select * from t1; +a +1 +2 +select * from t2; +a +1 +2 +select * from t3; +a +1 +2 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 6 +show status like "Qcache_hits"; +Variable_name Value +Qcache_hits 0 +insert into t1 values (3); +insert into t2 values (3); +insert into t1 values (4); +select * from t1; +a +1 +2 +3 +4 +select * from t2; +a +1 +2 +3 +select * from t3; +a +1 +2 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 4 +show status like "Qcache_hits"; +Variable_name Value +Qcache_hits 1 +commit; +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 4 +drop table t3,t2,t1; +CREATE TABLE t1 (id int(11) NOT NULL auto_increment, PRIMARY KEY (id)); +select count(*) from t1; +count(*) +0 +insert into t1 (id) values (0); +select count(*) from t1; +count(*) +1 +drop table t1; +SET SESSION STORAGE_ENGINE = MyISAM; +SET @@autocommit=1; +connection default +SHOW VARIABLES LIKE 'have_query_cache'; +Variable_name Value +have_query_cache YES +SET GLOBAL query_cache_size = 200000; +flush status; +SET @@autocommit=1; +SET SESSION STORAGE_ENGINE = MyISAM; +CREATE TABLE t2 (s1 int, s2 varchar(1000), key(s1)); +INSERT INTO t2 VALUES (1,repeat('a',10)),(2,repeat('a',10)),(3,repeat('a',10)),(4,repeat('a',10)); +COMMIT; +START TRANSACTION; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +0 +UPDATE t2 SET s2 = 'w' WHERE s1 = 3; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +1 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 1 +connection connection1 +START TRANSACTION; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +1 +INSERT INTO t2 VALUES (5,'w'); +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +COMMIT; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 2 +connection default +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +COMMIT; +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 2 +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 2 +connection connection1 +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +START TRANSACTION; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +INSERT INTO t2 VALUES (6,'w'); +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +3 +connection default +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +3 +START TRANSACTION; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +3 +DELETE from t2 WHERE s1=3; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +COMMIT; +connection connection1 +COMMIT; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 2 +show status like "Qcache_hits"; +Variable_name Value +Qcache_hits 6 +set GLOBAL query_cache_size=0; +drop table t2; === added file 'mysql-test/r/partition_cache_innodb.result' --- a/mysql-test/r/partition_cache_innodb.result 1970-01-01 00:00:00 +0000 +++ b/mysql-test/r/partition_cache_innodb.result 2011-05-08 21:23:01 +0000 @@ -0,0 +1,331 @@ +SET GLOBAL query_cache_size = 1024 * 1024; +SET SESSION STORAGE_ENGINE = InnoDB; +drop table if exists t1,t2,t3; +flush status; +set autocommit=0; +create table t1 (a int not null) PARTITION BY HASH (a) PARTITIONS 7; +insert into t1 values (1),(2),(3); +select * from t1; +a +1 +2 +3 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 0 +drop table t1; +commit; +set autocommit=1; +begin; +create table t1 (a int not null) PARTITION BY HASH (a) PARTITIONS 7; +insert into t1 values (1),(2),(3); +select * from t1; +a +1 +2 +3 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 1 +drop table t1; +commit; +create table t1 (a int not null) PARTITION BY HASH (a) PARTITIONS 7; +create table t2 (a int not null) PARTITION BY HASH (a) PARTITIONS 7; +create table t3 (a int not null) PARTITION BY HASH (a) PARTITIONS 7; +insert into t1 values (1),(2); +insert into t2 values (1),(2); +insert into t3 values (1),(2); +select * from t1; +a +1 +2 +select * from t2; +a +1 +2 +select * from t3; +a +1 +2 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 3 +show status like "Qcache_hits"; +Variable_name Value +Qcache_hits 0 +begin; +select * from t1; +a +1 +2 +select * from t2; +a +1 +2 +select * from t3; +a +1 +2 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 6 +show status like "Qcache_hits"; +Variable_name Value +Qcache_hits 0 +insert into t1 values (3); +insert into t2 values (3); +insert into t1 values (4); +select * from t1; +a +1 +2 +3 +4 +select * from t2; +a +1 +2 +3 +select * from t3; +a +1 +2 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 2 +show status like "Qcache_hits"; +Variable_name Value +Qcache_hits 1 +commit; +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 2 +drop table t3,t2,t1; +CREATE TABLE t1 (id int(11) NOT NULL auto_increment, PRIMARY KEY (id)) PARTITION BY HASH (id) PARTITIONS 7; +select count(*) from t1; +count(*) +0 +insert into t1 (id) values (0); +select count(*) from t1; +count(*) +1 +drop table t1; +SET SESSION STORAGE_ENGINE = InnoDB; +SET @@autocommit=1; +connection default +SHOW VARIABLES LIKE 'have_query_cache'; +Variable_name Value +have_query_cache YES +SET GLOBAL query_cache_size = 200000; +flush status; +SET @@autocommit=1; +SET SESSION STORAGE_ENGINE = InnoDB; +CREATE TABLE t2 (s1 int, s2 varchar(1000), key(s1)) PARTITION BY HASH (s1) PARTITIONS 7; +INSERT INTO t2 VALUES (1,repeat('a',10)),(2,repeat('a',10)),(3,repeat('a',10)),(4,repeat('a',10)); +COMMIT; +START TRANSACTION; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +0 +UPDATE t2 SET s2 = 'w' WHERE s1 = 3; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +1 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 0 +connection connection1 +START TRANSACTION; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +0 +INSERT INTO t2 VALUES (5,'w'); +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +1 +COMMIT; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +1 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 0 +connection default +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +1 +COMMIT; +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 0 +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 1 +connection connection1 +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +START TRANSACTION; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +INSERT INTO t2 VALUES (6,'w'); +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +3 +connection default +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +START TRANSACTION; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +DELETE from t2 WHERE s1=3; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +1 +COMMIT; +connection connection1 +COMMIT; +SELECT sql_cache count(*) FROM t2 WHERE s2 = 'w'; +count(*) +2 +show status like "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 1 +show status like "Qcache_hits"; +Variable_name Value +Qcache_hits 1 +set GLOBAL query_cache_size=0; +drop table t2; +SET GLOBAL query_cache_size = 1024 * 1024; +create table t1 +(name varchar(128), +dept int, +primary key(dept,name)) +ENGINE=InnoDB +PARTITION BY RANGE (`dept`) +( +PARTITION d0 VALUES LESS THAN (1), +PARTITION d1 VALUES LESS THAN (2), +PARTITION d2 VALUES LESS THAN (3), +PARTITION d_other VALUES LESS THAN MAXVALUE); +set autocommit=0; +start transaction; +insert into t1(name,dept) values('a',1); +insert into t1(name,dept) values('b',1); +insert into t1(name,dept) values('c',1); +select count(*) from t1; +count(*) +3 +# expecting 3 +rollback; +select count(*) from t1; +count(*) +0 +# expecting 0, and seeing 0 +select count(*) from t1; +count(*) +0 +# expecting 0, and seeing 3 before fix! +drop table t1; +CREATE TABLE `t1` +(`id` int(11) NOT NULL , +`created_at` datetime NOT NULL, +`cool` tinyint default 0 +) ENGINE=InnoDB; +ALTER TABLE t1 +PARTITION BY RANGE (TO_DAYS(created_at)) +(PARTITION month_2010_4 VALUES LESS THAN (734258), +PARTITION month_2010_5 VALUES LESS THAN (734289), +PARTITION month_max VALUES LESS THAN MAXVALUE); +INSERT INTO t1 VALUES (1, now(), 0); +BEGIN; +UPDATE `t1` SET `cool` = 1 WHERE `id` = 1; +SELECT cool FROM `t1` WHERE (`t1`.id = 1) LIMIT 1; +cool +1 +ROLLBACK; +SELECT cool FROM `t1` WHERE (`t1`.id = 1) LIMIT 1; +cool +0 +BEGIN; +SELECT cool FROM `t1` WHERE (`t1`.id = 1) LIMIT 1; +cool +0 +ROLLBACK; +SELECT cool FROM `t1` WHERE (`t1`.id = 1) LIMIT 1; +cool +0 +DROP TABLE `t1`; +CREATE TABLE t1 (a INT, b VARCHAR(64)) +ENGINE = InnoDB +PARTITION BY HASH (a) +PARTITIONS 5; +INSERT INTO t1 VALUES (11, 'Eleven'), (1, 'One'), (2, 'Two'), (12, 'aslasdrfa'), (7, 'sdfae'), (4, 'asdfees'), (14, '3asdf3'), (9, 'asdfeea'); +set autocommit=1; +FLUSH STATUS; +SELECT * FROM t1; +a b +11 Eleven +1 One +2 Two +12 aslasdrfa +7 sdfae +4 asdfees +14 3asdf3 +9 asdfeea +SHOW STATUS LIKE "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 1 +SHOW STATUS LIKE "Qcache_inserts"; +Variable_name Value +Qcache_inserts 1 +SHOW STATUS LIKE "Qcache_hits"; +Variable_name Value +Qcache_hits 0 +SELECT * FROM t1; +a b +11 Eleven +1 One +2 Two +12 aslasdrfa +7 sdfae +4 asdfees +14 3asdf3 +9 asdfeea +SHOW STATUS LIKE "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 1 +SHOW STATUS LIKE "Qcache_inserts"; +Variable_name Value +Qcache_inserts 1 +SHOW STATUS LIKE "Qcache_hits"; +Variable_name Value +Qcache_hits 1 +FLUSH TABLES; +SELECT * FROM t1; +a b +11 Eleven +1 One +2 Two +12 aslasdrfa +7 sdfae +4 asdfees +14 3asdf3 +9 asdfeea +SHOW STATUS LIKE "Qcache_queries_in_cache"; +Variable_name Value +Qcache_queries_in_cache 1 +SHOW STATUS LIKE "Qcache_inserts"; +Variable_name Value +Qcache_inserts 2 +SHOW STATUS LIKE "Qcache_hits"; +Variable_name Value +Qcache_hits 1 +DROP TABLE t1; +set GLOBAL query_cache_size=0; === removed file 'mysql-test/t/cache_innodb-master.opt' --- a/mysql-test/t/cache_innodb-master.opt 2006-08-16 12:58:49 +0000 +++ b/mysql-test/t/cache_innodb-master.opt 1970-01-01 00:00:00 +0000 @@ -1 +0,0 @@ ---set-variable=query_cache_size=1M === added file 'mysql-test/t/cache_myisam.test' --- a/mysql-test/t/cache_myisam.test 1970-01-01 00:00:00 +0000 +++ b/mysql-test/t/cache_myisam.test 2011-05-08 21:23:01 +0000 @@ -0,0 +1,7 @@ +--source include/have_query_cache.inc + +let $engine_type= MyISAM; +# MyISAM does NOT supports FOREIGN KEYs +let $test_foreign_keys= 0; + +--source include/query_cache.inc === added file 'mysql-test/t/partition_cache_innodb.test' --- a/mysql-test/t/partition_cache_innodb.test 1970-01-01 00:00:00 +0000 +++ b/mysql-test/t/partition_cache_innodb.test 2011-05-08 21:23:01 +0000 @@ -0,0 +1,110 @@ +--source include/have_query_cache.inc + +--source include/have_innodb.inc +--source include/have_partition.inc +let $engine_type= InnoDB; +# partitioned InnoDB does NOT supports FOREIGN KEYs +let $test_foreign_keys= 0; + +# Using SELECT to get a space as first character. +let $partitions_a= `SELECT ' PARTITION BY HASH (a) PARTITIONS 7'`; +let $partitions_id= `SELECT ' PARTITION BY HASH (id) PARTITIONS 7'`; +let $partitions_s1= `SELECT ' PARTITION BY HASH (s1) PARTITIONS 7'`; + +--source include/query_cache.inc + +let $save_query_cache_size=`select @@global.query_cache_size`; +SET GLOBAL query_cache_size = 1024 * 1024; +create table t1 +(name varchar(128), + dept int, + primary key(dept,name)) +ENGINE=InnoDB +PARTITION BY RANGE (`dept`) +( + PARTITION d0 VALUES LESS THAN (1), + PARTITION d1 VALUES LESS THAN (2), + PARTITION d2 VALUES LESS THAN (3), + PARTITION d_other VALUES LESS THAN MAXVALUE); + +set autocommit=0; +start transaction; + +insert into t1(name,dept) values('a',1); +insert into t1(name,dept) values('b',1); +insert into t1(name,dept) values('c',1); + +select count(*) from t1; +--echo # expecting 3 +rollback; + +select count(*) from t1; +--echo # expecting 0, and seeing 0 + +select count(*) from t1; +--echo # expecting 0, and seeing 3 before fix! + +drop table t1; + + + +CREATE TABLE `t1` +(`id` int(11) NOT NULL , + `created_at` datetime NOT NULL, + `cool` tinyint default 0 +) ENGINE=InnoDB; + +ALTER TABLE t1 +PARTITION BY RANGE (TO_DAYS(created_at)) +(PARTITION month_2010_4 VALUES LESS THAN (734258), + PARTITION month_2010_5 VALUES LESS THAN (734289), + PARTITION month_max VALUES LESS THAN MAXVALUE); + +INSERT INTO t1 VALUES (1, now(), 0); + +BEGIN; + +UPDATE `t1` SET `cool` = 1 WHERE `id` = 1; + +SELECT cool FROM `t1` WHERE (`t1`.id = 1) LIMIT 1; + +ROLLBACK; + +SELECT cool FROM `t1` WHERE (`t1`.id = 1) LIMIT 1; + +BEGIN; + +SELECT cool FROM `t1` WHERE (`t1`.id = 1) LIMIT 1; + +ROLLBACK; + +SELECT cool FROM `t1` WHERE (`t1`.id = 1) LIMIT 1; + +DROP TABLE `t1`; + +CREATE TABLE t1 (a INT, b VARCHAR(64)) +ENGINE = InnoDB +PARTITION BY HASH (a) +PARTITIONS 5; +INSERT INTO t1 VALUES (11, 'Eleven'), (1, 'One'), (2, 'Two'), (12, 'aslasdrfa'), (7, 'sdfae'), (4, 'asdfees'), (14, '3asdf3'), (9, 'asdfeea'); +set autocommit=1; +FLUSH STATUS; +SELECT * FROM t1; +SHOW STATUS LIKE "Qcache_queries_in_cache"; +SHOW STATUS LIKE "Qcache_inserts"; +SHOW STATUS LIKE "Qcache_hits"; +SELECT * FROM t1; +SHOW STATUS LIKE "Qcache_queries_in_cache"; +SHOW STATUS LIKE "Qcache_inserts"; +SHOW STATUS LIKE "Qcache_hits"; +FLUSH TABLES; +SELECT * FROM t1; +SHOW STATUS LIKE "Qcache_queries_in_cache"; +SHOW STATUS LIKE "Qcache_inserts"; +SHOW STATUS LIKE "Qcache_hits"; +DROP TABLE t1; +eval set GLOBAL query_cache_size=$save_query_cache_size; +--echo # TODO: Add tests of partition pruning of +--echo # 64, 65, 255, 256 and 1024 partitions +--echo # 0-64, 0-9+65, 0-9+255, 0-5+256 and 0-5+1024 +--echo # non pruned partitions) === modified file 'sql/ha_partition.cc' --- a/sql/ha_partition.cc 2011-04-29 07:48:26 +0000 +++ b/sql/ha_partition.cc 2011-05-08 21:23:01 +0000 @@ -392,7 +392,7 @@ bool ha_partition::initialize_partition( DBUG_RETURN(0); } else if (get_from_handler_file(table_share->normalized_path.str, - mem_root, false)) + mem_root, false, NULL)) { my_message(ER_UNKNOWN_ERROR, "Failed to read from the .par file", MYF(0)); DBUG_RETURN(1); @@ -1881,7 +1881,7 @@ uint ha_partition::del_ren_cre_table(con DBUG_RETURN(TRUE); } - if (get_from_handler_file(from, ha_thd()->mem_root, false)) + if (get_from_handler_file(from, ha_thd()->mem_root, false, NULL)) DBUG_RETURN(TRUE); DBUG_ASSERT(m_file_buffer); DBUG_PRINT("enter", ("from: (%s) to: (%s)", from, to)); @@ -2385,7 +2385,8 @@ error_end: /** Read the .par file to get the partitions engines and names - @param name Name of table file (without extention) + @param name Name of table file (without extention) + @param[out] name_buffer_length Length of the m_name_buffer @return Operation status @retval true Failure @@ -2395,7 +2396,7 @@ error_end: freed by the caller. m_name_buffer_ptr and m_tot_parts is also set. */ -bool ha_partition::read_par_file(const char *name) +bool ha_partition::read_par_file(const char *name, uint *name_buffer_length) { char buff[FN_REFLEN], *tot_name_len_offset; File file; @@ -2443,6 +2444,8 @@ bool ha_partition::read_par_file(const c goto err2; VOID(my_close(file, MYF(0))); m_file_buffer= file_buffer; // Will be freed in clear_handler_file() + if (name_buffer_length) + *name_buffer_length= uint4korr(tot_name_len_offset); m_name_buffer_ptr= tot_name_len_offset + PAR_WORD_SIZE; DBUG_RETURN(false); @@ -2524,16 +2527,22 @@ err: partitions. */ -bool ha_partition::get_from_handler_file(const char *name, MEM_ROOT *mem_root, - bool is_clone) +bool ha_partition::get_from_handler_file(const char *name, + MEM_ROOT *mem_root, + bool is_clone, + uint *name_buffer_length) { DBUG_ENTER("ha_partition::get_from_handler_file"); DBUG_PRINT("enter", ("table name: '%s'", name)); if (m_file_buffer) + { + if (name_buffer_length) + *name_buffer_length= uint4korr(m_name_buffer_ptr - PAR_WORD_SIZE); DBUG_RETURN(false); + } - if (read_par_file(name)) + if (read_par_file(name, name_buffer_length)) DBUG_RETURN(true); if (!is_clone && setup_engine_array(mem_root)) @@ -2588,7 +2597,7 @@ int ha_partition::open(const char *name, { char *name_buffer_ptr; int error= HA_ERR_INITIALIZATION; - uint alloc_len; + uint alloc_len, name_buffer_length; handler **file; char name_buff[FN_REFLEN]; bool is_not_tmp_table= (table_share->tmp_table == NO_TMP_TABLE); @@ -2600,7 +2609,8 @@ int ha_partition::open(const char *name, m_mode= mode; m_open_test_lock= test_if_locked; m_part_field_array= m_part_info->full_part_field_array; - if (get_from_handler_file(name, &table->mem_root, test(m_is_clone_of))) + if (get_from_handler_file(name, &table->mem_root, test(m_is_clone_of), + &name_buffer_length)) DBUG_RETURN(error); name_buffer_ptr= m_name_buffer_ptr; m_start_key.length= 0; @@ -2633,6 +2643,48 @@ int ha_partition::open(const char *name, } } + /* + Use table_share->ha_data to: + - share auto_increment_value among all handlers for the same table. + - store a copy of all partition names. + */ + if (is_not_tmp_table) + pthread_mutex_lock(&table_share->mutex); + if (!table_share->ha_data) + { + HA_DATA_PARTITION *ha_data; + /* currently only needed for auto_increment */ + table_share->ha_data= ha_data= (HA_DATA_PARTITION*) + alloc_root(&table_share->mem_root, + sizeof(HA_DATA_PARTITION)); + if (!ha_data) + { + if (is_not_tmp_table) + pthread_mutex_unlock(&table_share->mutex); + DBUG_RETURN(HA_ERR_INITIALIZATION); + } + DBUG_PRINT("info", ("table_share->ha_data 0x%p", ha_data)); + bzero(ha_data, sizeof(HA_DATA_PARTITION)); + table_share->ha_data_destroy= ha_data_partition_destroy; + VOID(pthread_mutex_init(&ha_data->LOCK_auto_inc, MY_MUTEX_INIT_FAST)); + ha_data->num_tot_parts= m_tot_parts; + /* Allocate space for all partition names + one extra '\0' at the end */ + ha_data->partition_names= (char*) alloc_root(&table_share->mem_root, + name_buffer_length + 1); + + if (!ha_data->partition_names) + { + if (is_not_tmp_table) + pthread_mutex_unlock(&table_share->mutex); + DBUG_RETURN(HA_ERR_INITIALIZATION); + } + memcpy((void *) ha_data->partition_names, (void *) m_name_buffer_ptr, + name_buffer_length); + memset((void *) (ha_data->partition_names + name_buffer_length), 0, 1); + } + if (is_not_tmp_table) + pthread_mutex_unlock(&table_share->mutex); + /* Initialize the bitmap we use to minimize ha_start_bulk_insert calls */ if (bitmap_init(&m_bulk_insert_started, NULL, m_tot_parts + 1, FALSE)) DBUG_RETURN(error); @@ -2735,32 +2787,6 @@ int ha_partition::open(const char *name, goto err_handler; /* - Use table_share->ha_data to share auto_increment_value among all handlers - for the same table. - */ - if (is_not_tmp_table) - pthread_mutex_lock(&table_share->mutex); - if (!table_share->ha_data) - { - HA_DATA_PARTITION *ha_data; - /* currently only needed for auto_increment */ - table_share->ha_data= ha_data= (HA_DATA_PARTITION*) - alloc_root(&table_share->mem_root, - sizeof(HA_DATA_PARTITION)); - if (!ha_data) - { - if (is_not_tmp_table) - pthread_mutex_unlock(&table_share->mutex); - goto err_handler; - } - DBUG_PRINT("info", ("table_share->ha_data 0x%p", ha_data)); - bzero(ha_data, sizeof(HA_DATA_PARTITION)); - table_share->ha_data_destroy= ha_data_partition_destroy; - VOID(pthread_mutex_init(&ha_data->LOCK_auto_inc, MY_MUTEX_INIT_FAST)); - } - if (is_not_tmp_table) - pthread_mutex_unlock(&table_share->mutex); - /* Some handlers update statistics as part of the open call. This will in some cases corrupt the statistics of the partition handler and thus to ensure we have correct statistics we call info from open after @@ -6339,6 +6365,9 @@ bool ha_partition::can_switch_engines() } +/**************************************************************************** + MODULE query cache +****************************************************************************/ /* Is table cache supported @@ -6355,6 +6384,302 @@ uint8 ha_partition::table_cache_type() } +/** + Callback for verifying that the partitioned table has not changed. + + @param thd THD + @param key table key (db\0table) + @param len length of key + @param engine_data Engine specific data + + @return True if result still is valid. +*/ + +static my_bool partition_qc_check_callback(THD *thd, + char *key, + uint len, + ulonglong *engine_data) +{ + TABLE_SHARE *share; + int dummy, is_still_valid= 0; + uint tot_parts, part_id= 0; + HA_DATA_PARTITION *ha_data; + const char *part_name= NULL; + + DBUG_ENTER("partition_qc_check_callback"); + share= get_table_share(thd, NULL, key, len, 0, &dummy); + if (!share) + { + DBUG_PRINT("info", ("db: %s table: %s no share found (qc_check_callback)", + key, key + strlen(key) + 1)); + DBUG_RETURN(FALSE); + } + + /* + Since ha_data->partition_names and ha_data->static_qc_callback + only are set once and never reset or changed it is safe to check + first without holding a mutex. + */ + ha_data= (HA_DATA_PARTITION*) share->ha_data; + if (ha_data && ha_data->static_qc_callback) + { + part_name= ha_data->partition_names; + tot_parts= ha_data->num_tot_parts; + } + else + { + pthread_mutex_lock(&share->mutex); + if (ha_data && ha_data->static_qc_callback) + { + part_name= ha_data->partition_names; + tot_parts= ha_data->num_tot_parts; + } + pthread_mutex_unlock(&share->mutex); + } + + /* Remove this if hit, might be possible right after a FLUSH TABLES? */ + DBUG_ASSERT(part_name && tot_parts); + + + /* loop through all names and their callbacks */ + if (part_name) + { + char part_name_key[FN_REFLEN], *part_name_start; + uint part_id= 0, part_count= 0; + memcpy(part_name_key, key, len); + part_name_start= part_name_key + len - 1; + while (*part_name) + { + if (*engine_data) + { + /* + Check if it is pruning was used (engine_data). + See register function below. + */ + if (tot_parts <= 64) + { + ulonglong pruned_engine_data= *engine_data; + if (!(pruned_engine_data & (1ULL << part_id))) + goto next_part; + } + else if (tot_parts <= 255) + { + uint8 *pruned_engine_data= (uint8*) engine_data; + DBUG_ASSERT(part_count < 8); + if (!pruned_engine_data[part_count]) + break; + if ((part_id + 1) != pruned_engine_data[part_count]) + goto next_part; + } + else + { + uint16 *pruned_engine_data= (uint16*) engine_data; + DBUG_ASSERT(part_count < 4); + if (!pruned_engine_data[part_count]) + break; + if ((part_id + 1) != pruned_engine_data[part_count]) + goto next_part; + } + } + /* create new key + lenght */ + create_partition_name(part_name_start, "", + part_name, NORMAL_PART_NAME, FALSE); + if (!ha_data->static_qc_callback(thd, part_name_key, + len + strlen(part_name_start), + engine_data)) + { + DBUG_PRINT("info", ("part: %s callback does not allow cached result", + part_name_start)); + goto err; + } + part_count++; +next_part: + part_name+= strlen(part_name) + 1; + part_id++; + } + is_still_valid= 1; + } +err: + release_table_share(share, RELEASE_NORMAL); + DBUG_RETURN(test(is_still_valid)); +} + + +/** + Register a table to be cached with a query. + + @param thd Thread + @param table_key dbname\0tablename\0 + @param key_length Lenght of table_key + @param engine_callback Function callback for checking if table is changed + @param engine_data Engine specific data + + @return True if the query can be cached + @retval TRUE OK to cache query + @retval FALSE can not cache query + + @note Since partitioning does not support foreign keys, there will not be any + internal invalidations by InnoDB. + + Implemented by forward the call to every partition, with its unique partition + name. If not all partitions return the same callback and engine_data we + cannot succeed (both MyISAM and InnoDB have a static callback and don't use + the engine_data). +*/ + +my_bool ha_partition::register_query_cache_table(THD *thd, char *table_key, + uint key_length, + qc_engine_callback + *engine_callback, + ulonglong *engine_data) +{ + qc_engine_callback first_callback, part_callback; + ulonglong first_data= 0, part_engine_data, pruned_engine_data= 0; + bool first_set= false; + handler **file; + char part_name_key[FN_REFLEN], *part_name_start; + const char *part_name= + ((HA_DATA_PARTITION*) table_share->ha_data)->partition_names; + DBUG_ENTER("ha_partition::register_query_cache_table"); + + /* + Check if it is possible to use pruning by using + the unused engine_data. + Like: + TODO: Fix this scheme, fails if only first partition is used! + Solve by moving part_id range with one -> 1..m_tot_parts instead + of 0..(m_tot_parts - 1) + If <= 64 parts use as bitmap. + If (> 64 parts && <= 255 parts && max 8 partitions used) use + each byte as (partition id + 1) (uint8). + If (> 255 parts && max 4 partitions used) use + each pair of byte as (partition id + 1) (uint16). + */ + if (m_tot_parts <= 64) + { + uint part_id= bitmap_get_first_set(&m_part_info->used_partitions); + for (;part_id < m_tot_parts; part_id++) + { + if (bitmap_is_set(&m_part_info->used_partitions, part_id)) + pruned_engine_data|= 1ULL << part_id; + } + } + else if (m_tot_parts <= 255) + { + if (bitmap_bits_set(&m_part_info->used_partitions) <= 8) + { + uint part_id= bitmap_get_first_set(&m_part_info->used_partitions); + uint part_count= 0; + uint8 *pruned_engine_data_array= (uint8*) &pruned_engine_data; + for (;part_id < m_tot_parts; part_id++) + { + if (bitmap_is_set(&m_part_info->used_partitions, part_id)) + pruned_engine_data_array[part_count++]= (part_id + 1); + DBUG_ASSERT(part_count <= 8); + } + } + } + else + { + if (bitmap_bits_set(&m_part_info->used_partitions) <= 4) + { + uint part_id= bitmap_get_first_set(&m_part_info->used_partitions); + uint part_count= 0; + uint16 *pruned_engine_data_array= (uint16*) &pruned_engine_data; + for (;part_id < m_tot_parts; part_id++) + { + if (bitmap_is_set(&m_part_info->used_partitions, part_id)) + pruned_engine_data_array[part_count++]= (part_id + 1); + DBUG_ASSERT(part_count <= 4); + } + } + } + file= m_file; + memcpy(part_name_key, table_key, key_length); + part_name_start= part_name_key + key_length - 1; + do + { + if (!pruned_engine_data || bitmap_is_set(&m_part_info->used_partitions, + file - m_file)) + { + create_partition_name(part_name_start, "", + part_name, NORMAL_PART_NAME, FALSE); + if (!(*file)->register_query_cache_table(thd, part_name_key, + key_length + + strlen(part_name_start), + &part_callback, + &part_engine_data)) + { + DBUG_PRINT("info", ("part: %s failed register_query_cache_table", + part_name_start)); + DBUG_RETURN(FALSE); + } + + /* + All partitions must have same callback function and engine_data. + MyISAM sets both callback and data = 0. + InnoDB uses a static function for callback and sets data = 0. + */ + if (!first_set) + { + first_callback= part_callback; + first_data= part_engine_data; + first_set= true; + } + if (first_callback != part_callback || first_data != part_engine_data) + { + DBUG_PRINT("info", ("part: %s different callback function/engine data", + part_name_start)); + DBUG_RETURN(FALSE); + } + } + part_name+= strlen(part_name) + 1; + + } while (*(++file)); + + if (first_callback) + { + HA_DATA_PARTITION *ha_data= (HA_DATA_PARTITION*) table_share->ha_data; + /* + Verify if it is the same as ha_data->qc_callback. + Or register if never set. + This is only set by the first call from the first instance + and never reset or changed, so it is safe to check first without + taking the table_share->mutex. + */ + DBUG_ASSERT(ha_data); + if (!ha_data->static_qc_callback) + { + pthread_mutex_lock(&table_share->mutex); + ha_data->static_qc_callback= first_callback; + pthread_mutex_unlock(&table_share->mutex); + } + + if (ha_data->static_qc_callback != first_callback) + { + /* If this is hit, the underlying engine have changed the callback! */ + DBUG_ASSERT(0); + DBUG_RETURN(FALSE); + } + + *engine_callback= partition_qc_check_callback; + } + + /* + Neither InnoDB or MyISAM use engine_data. + If set, we must remove the pruning optimization! + */ + DBUG_ASSERT(!first_data); + if (first_data) + { + DBUG_RETURN(FALSE); + } + + *engine_data= pruned_engine_data; + + DBUG_RETURN(TRUE); +} + /**************************************************************************** MODULE print messages ****************************************************************************/ @@ -6898,128 +7223,6 @@ int ha_partition::indexes_are_disabled(v return error; } - -/**************************************************************************** - MODULE Partition Share -****************************************************************************/ -/* - Service routines for ... methods. -------------------------------------------------------------------------- - Variables for partition share methods. A hash used to track open tables. - A mutex for the hash table and an init variable to check if hash table - is initialized. - There is also a constant ending of the partition handler file name. -*/ - -#ifdef NOT_USED -static HASH partition_open_tables; -static pthread_mutex_t partition_mutex; -static int partition_init= 0; - - -/* - Function we use in the creation of our hash to get key. -*/ - -static uchar *partition_get_key(PARTITION_SHARE *share, size_t *length, - my_bool not_used __attribute__ ((unused))) -{ - *length= share->table_name_length; - return (uchar *) share->table_name; -} - -/* - Example of simple lock controls. The "share" it creates is structure we - will pass to each partition 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 PARTITION_SHARE *get_share(const char *table_name, TABLE *table) -{ - PARTITION_SHARE *share; - uint length; - char *tmp_name; - - /* - So why does this exist? There is no way currently to init a storage - engine. - Innodb and BDB both have modifications to the server to allow them to - do this. Since you will not want to do this, this is probably the next - best method. - */ - if (!partition_init) - { - /* Hijack a mutex for init'ing the storage engine */ - pthread_mutex_lock(&LOCK_mysql_create_db); - if (!partition_init) - { - partition_init++; - VOID(pthread_mutex_init(&partition_mutex, MY_MUTEX_INIT_FAST)); - (void) hash_init(&partition_open_tables, system_charset_info, 32, 0, 0, - (hash_get_key) partition_get_key, 0, 0); - } - pthread_mutex_unlock(&LOCK_mysql_create_db); - } - pthread_mutex_lock(&partition_mutex); - length= (uint) strlen(table_name); - - if (!(share= (PARTITION_SHARE *) hash_search(&partition_open_tables, - (uchar *) table_name, length))) - { - if (!(share= (PARTITION_SHARE *) - my_multi_malloc(MYF(MY_WME | MY_ZEROFILL), - &share, (uint) sizeof(*share), - &tmp_name, (uint) length + 1, NullS))) - { - pthread_mutex_unlock(&partition_mutex); - return NULL; - } - - share->use_count= 0; - share->table_name_length= length; - share->table_name= tmp_name; - strmov(share->table_name, table_name); - if (my_hash_insert(&partition_open_tables, (uchar *) share)) - goto error; - thr_lock_init(&share->lock); - pthread_mutex_init(&share->mutex, MY_MUTEX_INIT_FAST); - } - share->use_count++; - pthread_mutex_unlock(&partition_mutex); - - return share; - -error: - pthread_mutex_unlock(&partition_mutex); - my_free((uchar*) share, MYF(0)); - - 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(PARTITION_SHARE *share) -{ - pthread_mutex_lock(&partition_mutex); - if (!--share->use_count) - { - hash_delete(&partition_open_tables, (uchar *) share); - thr_lock_delete(&share->lock); - pthread_mutex_destroy(&share->mutex); - my_free((uchar*) share, MYF(0)); - } - pthread_mutex_unlock(&partition_mutex); - - return 0; -} -#endif /* NOT_USED */ - struct st_mysql_storage_engine partition_storage_engine= { MYSQL_HANDLERTON_INTERFACE_VERSION }; === modified file 'sql/ha_partition.h' --- a/sql/ha_partition.h 2011-04-20 15:52:33 +0000 +++ b/sql/ha_partition.h 2011-05-08 21:23:01 +0000 @@ -22,20 +22,6 @@ enum partition_keywords PKW_HASH= 0, PKW_RANGE, PKW_LIST, PKW_KEY, PKW_MAXVALUE, PKW_LINEAR }; -/* - PARTITION_SHARE is a structure that will be shared amoung all open handlers - The partition implements the minimum of what you will probably need. -*/ - -#ifdef NOT_USED -typedef struct st_partition_share -{ - char *table_name; - uint table_name_length, use_count; - pthread_mutex_t mutex; - THR_LOCK lock; -} PARTITION_SHARE; -#endif /** Partition specific ha_data struct. @@ -46,6 +32,13 @@ typedef struct st_ha_data_partition ulonglong next_auto_inc_val; /**< first non reserved value */ pthread_mutex_t LOCK_auto_inc; bool auto_inc_initialized; + /* + All partition names in file-name format with '\0' in between and two + '\0' at the end. + */ + const char *partition_names; + qc_engine_callback static_qc_callback; + uint num_tot_parts; } HA_DATA_PARTITION; #define PARTITION_BYTES_IN_POS 2 @@ -292,9 +285,9 @@ private: */ bool create_handler_file(const char *name); bool setup_engine_array(MEM_ROOT *mem_root); - bool read_par_file(const char *name); + bool read_par_file(const char *name, uint *name_buffer_length); bool get_from_handler_file(const char *name, MEM_ROOT *mem_root, - bool is_clone); + bool is_clone, uint *name_buffer_length); bool new_handlers_from_part_info(MEM_ROOT *mem_root); bool create_handlers(MEM_ROOT *mem_root); void clear_handler_file(); @@ -627,6 +620,11 @@ public: underlying handlers must have the same implementation for it to work. */ virtual uint8 table_cache_type(); + virtual my_bool register_query_cache_table(THD *thd, char *table_key, + uint key_length, + qc_engine_callback + *engine_callback, + ulonglong *engine_data); virtual ha_rows records(); /* === modified file 'sql/sql_base.cc' --- a/sql/sql_base.cc 2011-03-29 08:09:05 +0000 +++ b/sql/sql_base.cc 2011-05-08 21:23:01 +0000 @@ -345,7 +345,7 @@ TABLE_SHARE *get_table_share(THD *thd, T key_length))) goto found; - if (!(share= alloc_table_share(table_list, key, key_length))) + if (!table_list || !(share= alloc_table_share(table_list, key, key_length))) { DBUG_RETURN(0); } --===============1327072748== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/mattias.jonsson@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: mattias.jonsson@stripped\ # mgo9udm02i4ug584 # target_branch: file:///C:/ade/mysql-bzr/b11761296-5.1/ # testament_sha1: 563a249982bccd248d3b53ae948cea0c60b291f7 # timestamp: 2011-05-08 23:23:11 +0200 # base_revision_id: alexander.nozdrin@stripped\ # pbwr2k9x04qdgxh8 # # Begin bundle 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 i7kinChIBoo/wgA= --===============1327072748==--