From a76224d7e6bf36a0a9a8a3418014fd6fd2505a38 Mon Sep 17 00:00:00 2001 From: "jianwei.zhao" Date: Thu, 3 Jan 2019 15:00:57 +0800 Subject: [PATCH] [Feature] SEQUENCE ENGINE Summary: Description: ------------ SEQUENCE engine as an embedded logical engine, it mainly used to generate unique number, it is the middle layer engine that use InnoDB or other storage engine as the based table engine, All query on sequence table will be changed into the operation on based table. the cache or other sequence value management is decided by SEQUENCE engine handler. According to the setting which is defined by 'CREATE SEQUENCE ... ' or 'CREATE TABLE...Engine=Sequence + INSERT VALUES'. user can query the nextval or currval from sequence. In order to distinguish the normal SELECT statement, we supply two functions; 1. 'SELECT NEXTVAL FROM SEQUENCE' will return the based table record directly. 2. 'SELECT NEXTVAL(), CURRVAL()' will return the iterated record. Syntax: ------- CREATE SEQUENCE SYNTAX: CREATE SEQUENCE [IF NOT EXISTS] schema.sequence_name [START WITH ] [MINVALUE ] [MAXVALUE ] [INCREMENT BY ] [CACHE | NOCACHE] [CYCLE | NOCYCLE] ; OR: CREATE TABLE schema.sequence_name ( `currval` bigint(21) NOT NULL COMMENT 'current value', `nextval` bigint(21) NOT NULL COMMENT 'next value', `minvalue` bigint(21) NOT NULL COMMENT 'min value', `maxvalue` bigint(21) NOT NULL COMMENT 'max value', `start` bigint(21) NOT NULL COMMENT 'start value', `increment` bigint(21) NOT NULL COMMENT 'increment value', `cache` bigint(21) NOT NULL COMMENT 'cache size', `cycle` bigint(21) NOT NULL COMMENT 'cycle state', `round` bigint(21) NOT NULL COMMENT 'already how many round' ) ENGINE=Sequence DEFAULT CHARSET=latin1 INSERT INTO schema.sequence_name VALUES(0,0,1,9223372036854775807,1,1,10000,1,0); COMMIT; Strongly recommend the first CREATE SEQUENCE syntax. SHOW SYNTAX: SHOW CREATE TABLE schema.sequence_name; QUERY SYNTAX: SELECT [nextval | currval | *] FROM schema.sequence_name; Usage: ------ FOR EXAMPLE: CREATE SEQUENCE s; INSERT INTO t VALUES(NEXTVAL(s)); --- include/my_base.h | 10 +- .../r/information_schema_keywords.result | 8 + mysql-test/r/udf.result | 18 +- mysql-test/r/variables.result | 1 + .../rds/r/feature_sequence_fundamental.result | 878 +++++++++++ .../rds/t/feature_sequence_fundamental.test | 840 +++++++++++ mysql-test/suite/sys_vars/r/all_vars.result | 1 + mysql-test/t/udf.test | 16 +- mysys/my_handler_errors.h | 6 +- share/errmsg-utf8.txt | 17 + sql/CMakeLists.txt | 9 + sql/dd_table_share.cc | 4 + sql/ha_sequence.cc | 1339 +++++++++++++++++ sql/ha_sequence.h | 567 +++++++ sql/handler.cc | 3 + sql/handler.h | 9 + sql/item_sequence_func.cc | 108 ++ sql/item_sequence_func.h | 74 + sql/lex.h | 8 + sql/mdl.cc | 19 + sql/mdl.h | 12 +- sql/parse_tree_nodes.h | 8 +- sql/sequence_common.cc | 312 ++++ sql/sequence_common.h | 278 ++++ sql/sequence_transaction.cc | 148 ++ sql/sequence_transaction.h | 84 ++ sql/sql_base.cc | 16 +- sql/sql_class.cc | 20 +- sql/sql_class.h | 29 + sql/sql_cmd_ddl_table.h | 2 +- sql/sql_lex.cc | 2 + sql/sql_lex.h | 12 +- sql/sql_parse.cc | 14 +- sql/sql_sequence.cc | 426 ++++++ sql/sql_sequence.h | 245 +++ sql/sql_table.cc | 36 +- sql/sql_yacc.yy | 120 +- sql/sys_vars.cc | 6 + sql/system_variables.h | 2 + sql/table.cc | 12 + sql/table.h | 16 + sql/udf_example.cc | 12 +- sql/udf_example.def | 6 +- 43 files changed, 5709 insertions(+), 44 deletions(-) create mode 100644 mysql-test/suite/rds/r/feature_sequence_fundamental.result create mode 100644 mysql-test/suite/rds/t/feature_sequence_fundamental.test create mode 100644 sql/ha_sequence.cc create mode 100644 sql/ha_sequence.h create mode 100644 sql/item_sequence_func.cc create mode 100644 sql/item_sequence_func.h create mode 100644 sql/sequence_common.cc create mode 100644 sql/sequence_common.h create mode 100644 sql/sequence_transaction.cc create mode 100644 sql/sequence_transaction.h create mode 100644 sql/sql_sequence.cc create mode 100644 sql/sql_sequence.h diff --git a/include/my_base.h b/include/my_base.h index 578aac3543a0..ef0c39a2d62c 100644 --- a/include/my_base.h +++ b/include/my_base.h @@ -921,7 +921,15 @@ is the global server default. */ #define HA_ERR_NO_WAIT_LOCK 203 /* Don't wait for record lock */ #define HA_ERR_DISK_FULL_NOWAIT 204 /* No more room in disk */ #define HA_ERR_NO_SESSION_TEMP 205 /* No session temporary space available */ -#define HA_ERR_LAST 205 /* Copy of last error nr */ + +/* These errors are only for Sequence Engine. */ +#define HA_ERR_SEQUENCE_RUN_OUT 206 /* Sequence has run out */ +#define HA_ERR_SEQUENCE_INVALID 207 /* Structure or number is invalid */ +#define HA_ERR_SEQUENCE_NOT_DEFINED 208 /* Sequence is not yet defined */ +#define HA_ERR_SEQUENCE_ACCESS_FAILURE 209 /* Sequence access failure*/ +/* Sequence Engine errors end */ + +#define HA_ERR_LAST 209 /* Copy of last error nr */ /* Number of different errors */ #define HA_ERR_ERRORS (HA_ERR_LAST - HA_ERR_FIRST + 1) diff --git a/mysql-test/r/information_schema_keywords.result b/mysql-test/r/information_schema_keywords.result index 3b63261b2ee2..77b0105132a9 100644 --- a/mysql-test/r/information_schema_keywords.result +++ b/mysql-test/r/information_schema_keywords.result @@ -102,8 +102,10 @@ CURRENT_DATE 1 CURRENT_TIME 1 CURRENT_TIMESTAMP 1 CURRENT_USER 1 +CURRVAL 0 CURSOR 1 CURSOR_NAME 0 +CYCLE 0 DATA 0 DATABASE 1 DATABASES 1 @@ -237,6 +239,7 @@ IGNORE 1 IGNORE_SERVER_IDS 0 IMPORT 0 IN 1 +INCREMENT 0 INDEX 1 INDEXES 0 INFILE 1 @@ -352,6 +355,7 @@ MIGRATE 0 MINUTE 0 MINUTE_MICROSECOND 1 MINUTE_SECOND 1 +MINVALUE 0 MIN_ROWS 0 MOD 1 MODE 0 @@ -374,7 +378,10 @@ NESTED 0 NEVER 0 NEW 0 NEXT 0 +NEXTVAL 0 NO 0 +NOCACHE 0 +NOCYCLE 0 NODEGROUP 0 NONE 0 NOT 1 @@ -527,6 +534,7 @@ SECURITY 0 SELECT 1 SENSITIVE 1 SEPARATOR 1 +SEQUENCE 0 SERIAL 0 SERIALIZABLE 0 SERVER 0 diff --git a/mysql-test/r/udf.result b/mysql-test/r/udf.result index 45498c2c5040..6212bbf47674 100644 --- a/mysql-test/r/udf.result +++ b/mysql-test/r/udf.result @@ -4,7 +4,7 @@ CREATE FUNCTION myfunc_double RETURNS REAL SONAME "UDF_EXAMPLE_LIB"; CREATE FUNCTION myfunc_nonexist RETURNS INTEGER SONAME "UDF_EXAMPLE_LIB"; ERROR HY000: Can't find symbol 'myfunc_nonexist' in library CREATE FUNCTION myfunc_int RETURNS INTEGER SONAME "UDF_EXAMPLE_LIB"; -CREATE FUNCTION sequence RETURNS INTEGER SONAME "UDF_EXAMPLE_LIB"; +CREATE FUNCTION sequence_alias RETURNS INTEGER SONAME "UDF_EXAMPLE_LIB"; CREATE FUNCTION lookup RETURNS STRING SONAME "UDF_EXAMPLE_LIB"; CREATE FUNCTION reverse_lookup RETURNS STRING SONAME "UDF_EXAMPLE_LIB"; @@ -21,7 +21,7 @@ metaphon char function UDF_EXAMPLE_LIB 1 myfunc_double double function UDF_EXAMPLE_LIB 1 myfunc_int integer function UDF_EXAMPLE_LIB 1 reverse_lookup char function UDF_EXAMPLE_LIB 1 -sequence integer function UDF_EXAMPLE_LIB 1 +sequence_alias integer function UDF_EXAMPLE_LIB 1 # Should get an index lookup EXPLAIN SELECT udf_type FROM performance_schema.user_defined_functions @@ -253,7 +253,7 @@ DROP FUNCTION myfunc_double; DROP FUNCTION myfunc_nonexist; ERROR 42000: FUNCTION test.myfunc_nonexist does not exist DROP FUNCTION myfunc_int; -DROP FUNCTION sequence; +DROP FUNCTION sequence_alias; DROP FUNCTION lookup; DROP FUNCTION reverse_lookup; DROP FUNCTION avgcost; @@ -367,32 +367,32 @@ DROP FUNCTION check_const_len; DROP PROCEDURE check_const_len_sp; DROP TRIGGER check_const_len_trigger; DROP TABLE const_len_bug; -CREATE FUNCTION sequence RETURNS INTEGER SONAME "UDF_EXAMPLE_LIB"; +CREATE FUNCTION sequence_alias RETURNS INTEGER SONAME "UDF_EXAMPLE_LIB"; CREATE TABLE t1 (a INT); CREATE TABLE t2 (a INT PRIMARY KEY); INSERT INTO t1 VALUES (4),(3),(2),(1); INSERT INTO t2 SELECT * FROM t1; -SELECT sequence() AS seq, a FROM t1 ORDER BY seq ASC; +SELECT sequence_alias() AS seq, a FROM t1 ORDER BY seq ASC; seq a 1 4 2 3 3 2 4 1 -SELECT sequence() AS seq, a FROM t1 ORDER BY seq DESC; +SELECT sequence_alias() AS seq, a FROM t1 ORDER BY seq DESC; seq a 4 1 3 2 2 3 1 4 -SELECT * FROM t1 WHERE a = sequence(); +SELECT * FROM t1 WHERE a = sequence_alias(); a -SELECT * FROM t2 WHERE a = sequence(); +SELECT * FROM t2 WHERE a = sequence_alias(); a 1 2 3 4 -DROP FUNCTION sequence; +DROP FUNCTION sequence_alias; DROP TABLE t1,t2; drop function if exists test.metaphon; drop function if exists metaphon; diff --git a/mysql-test/r/variables.result b/mysql-test/r/variables.result index 444972e4982c..678669103ecd 100644 --- a/mysql-test/r/variables.result +++ b/mysql-test/r/variables.result @@ -2019,6 +2019,7 @@ SET SESSION pseudo_thread_id = @@session.pseudo_thread_id; SET SESSION rand_seed1 = @@session.rand_seed1; SET SESSION rand_seed2 = @@session.rand_seed2; SET SESSION resultset_metadata = @@session.resultset_metadata; +SET SESSION sequence_read_skip_cache = @@session.sequence_read_skip_cache; SET SESSION sql_log_bin = @@session.sql_log_bin; SET SESSION timestamp = @@session.timestamp; SET SESSION transaction_allow_batching = @@session.transaction_allow_batching; diff --git a/mysql-test/suite/rds/r/feature_sequence_fundamental.result b/mysql-test/suite/rds/r/feature_sequence_fundamental.result new file mode 100644 index 000000000000..784fcd6f26c5 --- /dev/null +++ b/mysql-test/suite/rds/r/feature_sequence_fundamental.result @@ -0,0 +1,878 @@ +include/master-slave.inc +Warnings: +Note #### Sending passwords in plain text without SSL/TLS is extremely insecure. +Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information. +[connection master] +create database s_db; +create user normal_1@'%' identified by 'pass'; +create user normal_2@'%' identified by 'pass'; +create user normal_3@'%' identified by 'pass'; +create user normal_4@'%' identified by 'pass'; +grant all on s_db.* to normal_1@'%' ; +grant all on test.* to normal_2@'%' ; +grant all on s_db.* to normal_3@'%' ; +grant all on test.* to normal_4@'%' ; +SET @start_read_only = @@global.read_only; +SET global read_only = true; +########################################### +master and slave sync sequence. +########################################### +use s_db; +create sequence s1; +show create table s1; +Table Create Table +s1 CREATE TABLE `s1` ( + `currval` bigint(21) NOT NULL COMMENT 'current value', + `nextval` bigint(21) NOT NULL COMMENT 'next value', + `minvalue` bigint(21) NOT NULL COMMENT 'min value', + `maxvalue` bigint(21) NOT NULL COMMENT 'max value', + `start` bigint(21) NOT NULL COMMENT 'start value', + `increment` bigint(21) NOT NULL COMMENT 'increment value', + `cache` bigint(21) NOT NULL COMMENT 'cache size', + `cycle` bigint(21) NOT NULL COMMENT 'cycle state', + `round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=Sequence DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +use s_db; +show create table s1; +Table Create Table +s1 CREATE TABLE `s1` ( + `currval` bigint(21) NOT NULL COMMENT 'current value', + `nextval` bigint(21) NOT NULL COMMENT 'next value', + `minvalue` bigint(21) NOT NULL COMMENT 'min value', + `maxvalue` bigint(21) NOT NULL COMMENT 'max value', + `start` bigint(21) NOT NULL COMMENT 'start value', + `increment` bigint(21) NOT NULL COMMENT 'increment value', + `cache` bigint(21) NOT NULL COMMENT 'cache size', + `cycle` bigint(21) NOT NULL COMMENT 'cycle state', + `round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=Sequence DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +use s_db; +drop sequence s1; +########################################### +not support create table engine=sequence. +########################################### +create table t(id int)engine=sequence; +ERROR HY000: Sequence 's_db.t' structure or number is invalid. +create table t(id int)engine=innodb; +alter table t engine=sequence; +ERROR HY000: Sequence 'x.x' structure or number is invalid. +drop table t; +########################################### +not support alter sequence table. +########################################### +create sequence s2; +alter table s2 add id int; +ERROR HY000: Sequence 'x.x' structure or number is invalid. +alter table s2 add index ind_x(start); +ERROR HY000: Sequence 'x.x' structure or number is invalid. +drop sequence s2; +########################################### +support create sequence +########################################### +CREATE TABLE `s2` ( +`currval` bigint(21) NOT NULL COMMENT 'current value', +`nextval` bigint(21) NOT NULL COMMENT 'next value', +`minvalue` bigint(21) NOT NULL COMMENT 'min value', +`maxvalue` bigint(21) NOT NULL COMMENT 'max value', +`start` bigint(21) NOT NULL COMMENT 'start value', +`increment` bigint(21) NOT NULL COMMENT 'increment value', +`cache` bigint(21) NOT NULL COMMENT 'cache size', +`cycle` bigint(21) NOT NULL COMMENT 'cycle state', +`round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=Sequence DEFAULT CHARSET=latin1; +insert into s2 values(0, 0, 1, 10, 1, 2, 1, 1, 0); +commit; +select nextval(s2) ; +nextval(s2) +1 +select nextval(s2) ; +nextval(s2) +3 +select nextval(s2) ; +nextval(s2) +5 +select nextval(s2) ; +nextval(s2) +7 +select nextval(s2) ; +nextval(s2) +9 +select nextval(s2) ; +nextval(s2) +1 +select nextval(s2) ; +nextval(s2) +3 +select * from s2; +currval nextval minvalue maxvalue start increment cache cycle round +0 5 1 10 1 2 1 1 1 +select * from s2; +currval nextval minvalue maxvalue start increment cache cycle round +0 5 1 10 1 2 1 1 1 +drop sequence s2; +CREATE TABLE `s2` ( +`currval` bigint(21) NULL COMMENT 'current value', +`nextval` bigint(21) NOT NULL COMMENT 'next value', +`minvalue` bigint(21) NOT NULL COMMENT 'min value', +`maxvalue` bigint(21) NOT NULL COMMENT 'max value', +`start` bigint(21) NOT NULL COMMENT 'start value', +`increment` bigint(21) NOT NULL COMMENT 'increment value', +`cache` bigint(21) NOT NULL COMMENT 'cache size', +`cycle` bigint(21) NOT NULL COMMENT 'cycle state', +`round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=Sequence DEFAULT CHARSET=latin1; +ERROR HY000: Sequence 's_db.s2' structure or number is invalid. +CREATE TABLE `s2` ( +`rrval` bigint(21) NULL COMMENT 'current value', +`nextval` bigint(21) NOT NULL COMMENT 'next value', +`minvalue` bigint(21) NOT NULL COMMENT 'min value', +`maxvalue` bigint(21) NOT NULL COMMENT 'max value', +`start` bigint(21) NOT NULL COMMENT 'start value', +`increment` bigint(21) NOT NULL COMMENT 'increment value', +`cache` bigint(21) NOT NULL COMMENT 'cache size', +`cycle` bigint(21) NOT NULL COMMENT 'cycle state', +`round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=Sequence DEFAULT CHARSET=latin1; +ERROR HY000: Sequence 's_db.s2' structure or number is invalid. +CREATE TABLE `s2` ( +`currval` bigint(21) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'current value', +`nextval` bigint(21) NOT NULL COMMENT 'next value', +`minvalue` bigint(21) NOT NULL COMMENT 'min value', +`maxvalue` bigint(21) NOT NULL COMMENT 'max value', +`start` bigint(21) NOT NULL COMMENT 'start value', +`increment` bigint(21) NOT NULL COMMENT 'increment value', +`cache` bigint(21) NOT NULL COMMENT 'cache size', +`cycle` bigint(21) NOT NULL COMMENT 'cycle state', +`round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=Sequence DEFAULT CHARSET=latin1; +ERROR HY000: Sequence 's_db.s2' structure or number is invalid. +CREATE TABLE `s2` ( +`currval` bigint(21) NOT NULL COMMENT 'current value', +`nextval` bigint(21) NOT NULL COMMENT 'next value', +`minvalue` bigint(21) NOT NULL COMMENT 'min value', +`maxvalue` bigint(21) NOT NULL COMMENT 'max value', +`start` bigint(21) NOT NULL COMMENT 'start value', +`increment` bigint(21) NOT NULL COMMENT 'increment value', +`cache` bigint(21) NOT NULL COMMENT 'cache size', +`cycle` bigint(21) NOT NULL COMMENT 'cycle state', +`round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=sequence DEFAULT CHARSET=latin1; +show create table s2; +Table Create Table +s2 CREATE TABLE `s2` ( + `currval` bigint(21) NOT NULL COMMENT 'current value', + `nextval` bigint(21) NOT NULL COMMENT 'next value', + `minvalue` bigint(21) NOT NULL COMMENT 'min value', + `maxvalue` bigint(21) NOT NULL COMMENT 'max value', + `start` bigint(21) NOT NULL COMMENT 'start value', + `increment` bigint(21) NOT NULL COMMENT 'increment value', + `cache` bigint(21) NOT NULL COMMENT 'cache size', + `cycle` bigint(21) NOT NULL COMMENT 'cycle state', + `round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=Sequence DEFAULT CHARSET=latin1 +drop sequence s2; +########################################### +select sequence syntax test +########################################### +create sequence s2; +create table t2 (id int); +select * from s2; +currval nextval minvalue maxvalue start increment cache cycle round +0 0 1 9223372036854775807 1 1 10000 0 0 +select * from t2; +id +insert into t2 select nextval(s2); +commit; +select * from t2; +id +1 +select nextval(s2); +nextval(s2) +2 +select nextval(t2); +ERROR HY000: Table 's_db.t2' should be sequence +select * from s2, t2; +currval nextval minvalue maxvalue start increment cache cycle round id +0 10002 1 9223372036854775807 1 1 10000 0 0 1 +drop sequence s2; +drop table t2; +########################################### +support rename, not support truncate +########################################### +create sequence s2; +alter table s2 rename to s2_1; +select nextval(s2_1); +nextval(s2_1) +1 +rename table s2_1 to s2_2; +select nextval(s2_2); +nextval(s2_2) +10002 +truncate table s2_2; +ERROR HY000: Table storage engine for 's2_2' doesn't have this option +rename table s2_2 to s2; +drop sequence s2; +########################################### +not support create temproary sequence. +########################################### +create temporary sequence s2; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'sequence s2' at line 1 +########################################### +all invalid sequence value +########################################### +use s_db; +create sequence s2 start with 1 +minvalue 1 +maxvalue 100000 +increment by 1 +cache 10000 +cycle; +drop sequence s2; +create sequence s2 start with 1 +minvalue 1 +maxvalue 100000 +increment by 1 +cache 10000 +nocycle; +drop sequence s2; +create sequence s2 start with 1 +minvalue 1 +maxvalue 100000 +increment by 1 +nocache +nocycle; +drop sequence s2; +create sequence s2 start with 1 +minvalue 5 +maxvalue 100000 +increment by 1 +nocache +nocycle; +ERROR HY000: Sequence 's_db.s2' structure or number is invalid. +create sequence s2 start with 1 +minvalue 5 +maxvalue 5 +increment by 1 +nocache +nocycle; +ERROR HY000: Sequence 's_db.s2' structure or number is invalid. +create sequence s2 start with 1 +minvalue 5 +maxvalue 4 +increment by 1 +nocache +nocycle; +ERROR HY000: Sequence 's_db.s2' structure or number is invalid. +create sequence s2 start with 1 +minvalue 5 +maxvalue 4 +increment by 0 +nocache +nocycle; +ERROR HY000: Sequence 's_db.s2' structure or number is invalid. +########################################### +global read lock prevent query sequence +########################################### +use s_db; +create sequence s_db.s1; +flush table with read lock; +select nextval(s1); +ERROR HY000: Can't execute the query because you have a conflicting read lock +unlock tables; +drop sequence s1; +########################################### +session setting +########################################### +use s_db; +create sequence s1; +set session sequence_read_skip_cache=true; +select nextval(s1); +nextval(s1) +0 +select currval(s1); +currval(s1) +0 +set session sequence_read_skip_cache=false; +select nextval(s1); +nextval(s1) +1 +select currval(s1); +currval(s1) +1 +drop sequence s1; +########################################### +priv test +########################################### +create sequence s_db.s1; +select * from s_db.s1; +currval nextval minvalue maxvalue start increment cache cycle round +0 0 1 9223372036854775807 1 1 10000 0 0 +create sequence s_db.s2; +drop sequence s_db.s2; +select nextval(s_db.s1); +ERROR 42000: SELECT command denied to user 'normal_2'@'localhost' for table 's1' +create sequence s_db.s2; +ERROR 42000: CREATE command denied to user 'normal_2'@'localhost' for table 's2' +drop sequence s_db.s1; +########################################### +run out sequence value +########################################### +use s_db; +create sequence s_t start with 1 cache 2 maxvalue 5; +create table t(id int); +insert into t values(1111); +insert into t select nextval(s_t); +insert into t select nextval(s_t); +insert into t select nextval(s_t); +insert into t select nextval(s_t); +insert into t select nextval(s_t); +insert into t select nextval(s_t); +ERROR HY000: Sequence 's_db.s_t' has run out. +insert into t select nextval(s_t); +ERROR HY000: Sequence 's_db.s_t' has run out. +commit; +select * from t; +id +1111 +1 +2 +3 +4 +5 +use s_db; +select * from t; +id +1111 +1 +2 +3 +4 +5 +use s_db; +drop sequence s_t; +drop table t; +########################################### +read_only prevent query sequence +########################################### +create sequence s_db.s1 nocache; +show global variables like 'read_only'; +Variable_name Value +read_only OFF +select nextval(s_db.s1); +nextval(s_db.s1) +1 +show global variables like 'read_only'; +Variable_name Value +read_only ON +select nextval(s_db.s1); +ERROR HY000: The MySQL server is running with the --read-only option so it cannot execute this statement +drop sequence s_db.s1; +########################################### +update based table +########################################### +use s_db; +create sequence s_t start with 1 minvalue 1 maxvalue 20 increment by 1 cache 5 cycle; +use s_db; +select * from s_t; +currval nextval minvalue maxvalue start increment cache cycle round +0 0 1 20 1 1 5 1 0 +select nextval(s_t); +nextval(s_t) +1 +select * from s_t; +currval nextval minvalue maxvalue start increment cache cycle round +0 7 1 20 1 1 5 1 0 +------------------------------------------ +master update nextval; +------------------------------------------ +select nextval(s_t); +nextval(s_t) +2 +update s_t set nextval= 11; +commit; +select * from s_t; +currval nextval minvalue maxvalue start increment cache cycle round +0 11 1 20 1 1 5 1 0 +------------------------------------------ +show slave nextval; +------------------------------------------ +select * from s_t; +currval nextval minvalue maxvalue start increment cache cycle round +0 11 1 20 1 1 5 1 0 +set session sequence_read_skip_cache=off; +select nextval(s_t); +nextval(s_t) +11 +select * from s_t; +currval nextval minvalue maxvalue start increment cache cycle round +0 17 1 20 1 1 5 1 0 +------------------------------------------ +update into invalid sequence +------------------------------------------ +select nextval(s_t); +nextval(s_t) +12 +update s_t set nextval= 11,start=10, minvalue=11; +commit; +create table t_1(id int); +insert into t_1 value(1111); +select nextval(s_t); +ERROR HY000: Sequence 's_db.s_t' structure or number is invalid. +insert into t_1 select nextval(s_t); +ERROR HY000: Sequence 's_db.s_t' structure or number is invalid. +commit; +select * from t_1; +id +1111 +------------------------------------------ +delete sequence row +------------------------------------------ +delete from s_t; +commit; +select nextval(s_t); +ERROR HY000: Can't find record in 's_t' +drop sequence s_t; +drop table t_1; +########################################### +test transaction context (myisam) +########################################### +------------------------------------------ +transaction table and sequence +normal transaction commit +------------------------------------------ +use s_db; +set session sequence_read_skip_cache=off; +create sequence s_1 cache 5; +create table t_1(id int)engine=myisam; +begin; +insert into t_1 values(1111); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 values(2222); +commit; +select * from t_1; +id +1111 +1 +2 +2222 +set session sequence_read_skip_cache=off; +use s_db; +select * from t_1; +id +1111 +1 +2 +2222 +------------------------------------------ +normal transaction rollback +------------------------------------------ +begin; +insert into t_1 values(3333); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +select * from t_1; +id +1111 +1 +2 +2222 +3333 +3 +4 +5 +6 +7 +8 +9 +10 +rollback; +Warnings: +Warning 1196 Some non-transactional changed tables couldn't be rolled back +select * from t_1; +id +1111 +1 +2 +2222 +3333 +3 +4 +5 +6 +7 +8 +9 +10 +select nextval(s_1); +nextval(s_1) +11 +set session sequence_read_skip_cache=off; +use s_db; +select * from t_1; +id +1111 +1 +2 +2222 +3333 +3 +4 +5 +6 +7 +8 +9 +10 +use s_db; +drop sequence s_1; +drop table t_1; +########################################### +close binlog +########################################### +use s_db; +create sequence s1 cache 2; +select nextval(s1); +nextval(s1) +1 +select nextval(s1); +nextval(s1) +2 +select nextval(s1); +nextval(s1) +3 +select nextval(s1); +nextval(s1) +4 +commit; +select * from s1; +currval nextval minvalue maxvalue start increment cache cycle round +0 7 1 9223372036854775807 1 1 2 0 0 +use s_db; +select * from s1; +currval nextval minvalue maxvalue start increment cache cycle round +0 7 1 9223372036854775807 1 1 2 0 0 +------------------------------------------ +close session binlog. +------------------------------------------ +set session sql_log_bin=off; +select nextval(s1); +nextval(s1) +5 +select nextval(s1); +nextval(s1) +6 +select nextval(s1); +nextval(s1) +7 +select nextval(s1); +nextval(s1) +8 +set session sql_log_bin=on; +select * from s1; +currval nextval minvalue maxvalue start increment cache cycle round +0 10 1 9223372036854775807 1 1 2 0 0 +use s_db; +select * from s1; +currval nextval minvalue maxvalue start increment cache cycle round +0 7 1 9223372036854775807 1 1 2 0 0 +use s_db; +drop sequence s1; +########################################### +statement binlog +########################################### +------------------------------------------ +set binlog_format=statement +------------------------------------------ +set session sequence_read_skip_cache=off; +set session binlog_format=statement; +show session variables like '%binlog_format%'; +Variable_name Value +binlog_format STATEMENT +create sequence s1 cache 2; +select nextval(s1); +ERROR HY000: Sequence requires binlog_format = row +set session binlog_format=row; +select nextval(s1); +nextval(s1) +1 +use s_db; +select * from s1; +currval nextval minvalue maxvalue start increment cache cycle round +0 4 1 9223372036854775807 1 1 2 0 0 +set session sequence_read_skip_cache=off; +use s_db; +drop sequence s1; +------------------------------------------ +set binlog_format=mixed +------------------------------------------ +set session sequence_read_skip_cache=off; +set session binlog_format=mixed; +show session variables like '%binlog_format%'; +Variable_name Value +binlog_format MIXED +create sequence s1 cache 2; +select nextval(s1); +ERROR HY000: Sequence requires binlog_format = row +set session binlog_format=row; +select nextval(s1); +nextval(s1) +1 +use s_db; +select * from s1; +currval nextval minvalue maxvalue start increment cache cycle round +0 4 1 9223372036854775807 1 1 2 0 0 +set session sequence_read_skip_cache=off; +use s_db; +drop sequence s1; +########################################### +test savepoint +########################################### +set session sequence_read_skip_cache=off; +set session binlog_format=row; +create sequence s1 cache 2; +create table t1(id int)engine=innodb; +begin; +insert into t1 values(1111); +savepoint sp1; +insert into t1 select nextval(s1); +insert into t1 select nextval(s1); +insert into t1 select nextval(s1); +insert into t1 values(2222); +select * from t1; +id +1111 +1 +2 +3 +2222 +rollback to sp1; +select * from t1; +id +1111 +select nextval(s1); +nextval(s1) +4 +commit; +drop sequence s1; +drop table t1; +########################################### +test proc +########################################### +set session sequence_read_skip_cache=off; +use s_db; +create table t(id int)engine=innodb; +create procedure p1() +begin +create sequence s1 cache 2; +end// +create procedure p2() +begin +insert into t select nextval(s1); +commit; +end// +call p1(); +call p2(); +call p2(); +call p2(); +call p2(); +select * from t; +id +1 +2 +3 +4 +use s_db; +select * from t; +id +1 +2 +3 +4 +drop table t; +drop sequence s1; +drop procedure p1; +drop procedure p2; +########################################### +test trigger +########################################### +set session sequence_read_skip_cache=off; +use s_db; +create sequence s1 cache 2; +create table t1(id int)engine=innodb; +create table t2(id int)engine=innodb; +CREATE TRIGGER tri_1 +before INSERT ON t2 FOR EACH ROW +BEGIN +INSERT INTO t1 select nextval(s1); +END// +begin; +insert into t2 values(1111); +insert into t2 values(1111); +insert into t2 values(1111); +insert into t2 values(1111); +select * from t2; +id +1111 +1111 +1111 +1111 +select * from t1; +id +1 +2 +3 +4 +rollback; +select * from t2; +id +select * from t1; +id +select nextval(s1); +nextval(s1) +5 +drop trigger tri_1; +drop table t1; +drop table t2; +drop sequence s1; +########################################### +test value boundary +########################################### +use s_db; +------------------------------------------ +round increment by round +------------------------------------------ +create sequence s1 start with 5 minvalue 2 maxvalue 7 cache 1 cycle; +select nextval(s1); +nextval(s1) +5 +select nextval(s1); +nextval(s1) +6 +select nextval(s1); +nextval(s1) +7 +select nextval(s1); +nextval(s1) +2 +select nextval(s1); +nextval(s1) +3 +drop sequence s1; +create sequence s1 start with 5 minvalue 2 maxvalue 7 cache 10 nocycle; +select nextval(s1); +nextval(s1) +5 +select nextval(s1); +nextval(s1) +6 +select nextval(s1); +nextval(s1) +7 +select nextval(s1); +ERROR HY000: Sequence 's_db.s1' has run out. +drop sequence s1; +create sequence s1 start with 2 minvalue 1 maxvalue 3 increment by 3 nocache cycle; +select nextval(s1); +nextval(s1) +2 +select nextval(s1); +nextval(s1) +1 +select nextval(s1); +nextval(s1) +1 +select nextval(s1); +nextval(s1) +1 +select nextval(s1); +nextval(s1) +1 +drop sequence s1; +create sequence s1 start with 2 minvalue 1 maxvalue 3 increment by 3 cache 2 nocycle; +select nextval(s1); +nextval(s1) +2 +select nextval(s1); +ERROR HY000: Sequence 's_db.s1' has run out. +drop sequence s1; +------------------------------------------ +beyond ulonglong maxvalue +------------------------------------------ +create sequence s1 start with 9223372036854775805 minvalue 9223372036854775804 maxvalue 9223372036854775807 cache 1 cycle; +select nextval(s1); +nextval(s1) +9223372036854775805 +select nextval(s1); +nextval(s1) +9223372036854775806 +select nextval(s1); +nextval(s1) +9223372036854775807 +select nextval(s1); +nextval(s1) +9223372036854775804 +select nextval(s1); +nextval(s1) +9223372036854775805 +select nextval(s1); +nextval(s1) +9223372036854775806 +select nextval(s1); +nextval(s1) +9223372036854775807 +select nextval(s1); +nextval(s1) +9223372036854775804 +select nextval(s1); +nextval(s1) +9223372036854775805 +select nextval(s1); +nextval(s1) +9223372036854775806 +drop sequence s1; +create sequence s1 start with 9223372036854775805 minvalue 9223372036854775804 maxvalue 9223372036854775807 cache 10 cycle; +select nextval(s1); +nextval(s1) +9223372036854775805 +select nextval(s1); +nextval(s1) +9223372036854775806 +select nextval(s1); +nextval(s1) +9223372036854775807 +select nextval(s1); +nextval(s1) +9223372036854775804 +select nextval(s1); +nextval(s1) +9223372036854775805 +select nextval(s1); +nextval(s1) +9223372036854775806 +select nextval(s1); +nextval(s1) +9223372036854775807 +select nextval(s1); +nextval(s1) +9223372036854775804 +select nextval(s1); +nextval(s1) +9223372036854775805 +select nextval(s1); +nextval(s1) +9223372036854775806 +drop sequence s1; +drop database s_db; +drop user normal_1@'%'; +drop user normal_2@'%'; +drop user normal_3@'%'; +drop user normal_4@'%'; +set global read_only = @start_read_only; +include/rpl_end.inc diff --git a/mysql-test/suite/rds/t/feature_sequence_fundamental.test b/mysql-test/suite/rds/t/feature_sequence_fundamental.test new file mode 100644 index 000000000000..ff8bc63da995 --- /dev/null +++ b/mysql-test/suite/rds/t/feature_sequence_fundamental.test @@ -0,0 +1,840 @@ +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + + + +connection master; +create database s_db; +create user normal_1@'%' identified by 'pass'; +create user normal_2@'%' identified by 'pass'; +create user normal_3@'%' identified by 'pass'; +create user normal_4@'%' identified by 'pass'; + +grant all on s_db.* to normal_1@'%' ; +grant all on test.* to normal_2@'%' ; +grant all on s_db.* to normal_3@'%' ; +grant all on test.* to normal_4@'%' ; + +--sync_slave_with_master + +connect(m_normal_1, 127.0.0.1, normal_1, pass, s_db, $MASTER_MYPORT); +connect(m_normal_2, 127.0.0.1, normal_2, pass, test, $MASTER_MYPORT); + +connect(s_normal_3, 127.0.0.1, normal_3, pass, s_db, $SLAVE_MYPORT); +connect(s_normal_4, 127.0.0.1, normal_4, pass, test, $SLAVE_MYPORT); + + +connection slave; +SET @start_read_only = @@global.read_only; +SET global read_only = true; + +--echo ########################################### +--echo master and slave sync sequence. +--echo ########################################### + +connection master; +use s_db; + +create sequence s1; +show create table s1; + +--sync_slave_with_master +connection slave; +use s_db; +show create table s1; + +connection master; +use s_db; +drop sequence s1; + + +--echo ########################################### +--echo not support create table engine=sequence. +--echo ########################################### +connection master; + +--error ER_SEQUENCE_INVALID +create table t(id int)engine=sequence; + +create table t(id int)engine=innodb; + +--replace_regex /'.*'/'x.x'/ +--error ER_SEQUENCE_INVALID +alter table t engine=sequence; + +drop table t; + +--echo ########################################### +--echo not support alter sequence table. +--echo ########################################### +connection master; + +create sequence s2; + +--replace_regex /'.*'/'x.x'/ +--error ER_SEQUENCE_INVALID +alter table s2 add id int; + +--replace_regex /'.*'/'x.x'/ +--error ER_SEQUENCE_INVALID +alter table s2 add index ind_x(start); + +drop sequence s2; + + +--echo ########################################### +--echo support create sequence +--echo ########################################### +connection master; + +CREATE TABLE `s2` ( + `currval` bigint(21) NOT NULL COMMENT 'current value', + `nextval` bigint(21) NOT NULL COMMENT 'next value', + `minvalue` bigint(21) NOT NULL COMMENT 'min value', + `maxvalue` bigint(21) NOT NULL COMMENT 'max value', + `start` bigint(21) NOT NULL COMMENT 'start value', + `increment` bigint(21) NOT NULL COMMENT 'increment value', + `cache` bigint(21) NOT NULL COMMENT 'cache size', + `cycle` bigint(21) NOT NULL COMMENT 'cycle state', + `round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=Sequence DEFAULT CHARSET=latin1; + +insert into s2 values(0, 0, 1, 10, 1, 2, 1, 1, 0); +commit; +select nextval(s2) ; +select nextval(s2) ; +select nextval(s2) ; +select nextval(s2) ; +select nextval(s2) ; +select nextval(s2) ; +select nextval(s2) ; + +connection master; +--sync_slave_with_master +select * from s2; + +connection slave; +select * from s2; + + +connection master; +drop sequence s2; + +--error ER_SEQUENCE_INVALID +CREATE TABLE `s2` ( + `currval` bigint(21) NULL COMMENT 'current value', + `nextval` bigint(21) NOT NULL COMMENT 'next value', + `minvalue` bigint(21) NOT NULL COMMENT 'min value', + `maxvalue` bigint(21) NOT NULL COMMENT 'max value', + `start` bigint(21) NOT NULL COMMENT 'start value', + `increment` bigint(21) NOT NULL COMMENT 'increment value', + `cache` bigint(21) NOT NULL COMMENT 'cache size', + `cycle` bigint(21) NOT NULL COMMENT 'cycle state', + `round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=Sequence DEFAULT CHARSET=latin1; + +--error ER_SEQUENCE_INVALID +CREATE TABLE `s2` ( + `rrval` bigint(21) NULL COMMENT 'current value', + `nextval` bigint(21) NOT NULL COMMENT 'next value', + `minvalue` bigint(21) NOT NULL COMMENT 'min value', + `maxvalue` bigint(21) NOT NULL COMMENT 'max value', + `start` bigint(21) NOT NULL COMMENT 'start value', + `increment` bigint(21) NOT NULL COMMENT 'increment value', + `cache` bigint(21) NOT NULL COMMENT 'cache size', + `cycle` bigint(21) NOT NULL COMMENT 'cycle state', + `round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=Sequence DEFAULT CHARSET=latin1; + +--error ER_SEQUENCE_INVALID +CREATE TABLE `s2` ( + `currval` bigint(21) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'current value', + `nextval` bigint(21) NOT NULL COMMENT 'next value', + `minvalue` bigint(21) NOT NULL COMMENT 'min value', + `maxvalue` bigint(21) NOT NULL COMMENT 'max value', + `start` bigint(21) NOT NULL COMMENT 'start value', + `increment` bigint(21) NOT NULL COMMENT 'increment value', + `cache` bigint(21) NOT NULL COMMENT 'cache size', + `cycle` bigint(21) NOT NULL COMMENT 'cycle state', + `round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=Sequence DEFAULT CHARSET=latin1; + +CREATE TABLE `s2` ( + `currval` bigint(21) NOT NULL COMMENT 'current value', + `nextval` bigint(21) NOT NULL COMMENT 'next value', + `minvalue` bigint(21) NOT NULL COMMENT 'min value', + `maxvalue` bigint(21) NOT NULL COMMENT 'max value', + `start` bigint(21) NOT NULL COMMENT 'start value', + `increment` bigint(21) NOT NULL COMMENT 'increment value', + `cache` bigint(21) NOT NULL COMMENT 'cache size', + `cycle` bigint(21) NOT NULL COMMENT 'cycle state', + `round` bigint(21) NOT NULL COMMENT 'already how many round' +) ENGINE=sequence DEFAULT CHARSET=latin1; + +show create table s2; +drop sequence s2; + + +--echo ########################################### +--echo select sequence syntax test +--echo ########################################### +connection master; +create sequence s2; +create table t2 (id int); + +select * from s2; +select * from t2; +insert into t2 select nextval(s2); +commit; + +select * from t2; +select nextval(s2); + +--error ER_TABLE_IS_NOT_SEQUENCE +select nextval(t2); + +select * from s2, t2; + +connection master; +drop sequence s2; +drop table t2; + +--echo ########################################### +--echo support rename, not support truncate +--echo ########################################### +connection master; + +create sequence s2; + +alter table s2 rename to s2_1; +select nextval(s2_1); + +rename table s2_1 to s2_2; +select nextval(s2_2); + +--error ER_ILLEGAL_HA +truncate table s2_2; +rename table s2_2 to s2; +drop sequence s2; + +--echo ########################################### +--echo not support create temproary sequence. +--echo ########################################### +connection master; + +--error 1064 +create temporary sequence s2; + + +--echo ########################################### +--echo all invalid sequence value +--echo ########################################### + +connection master; +use s_db; +create sequence s2 start with 1 + minvalue 1 + maxvalue 100000 + increment by 1 + cache 10000 + cycle; +drop sequence s2; +create sequence s2 start with 1 + minvalue 1 + maxvalue 100000 + increment by 1 + cache 10000 + nocycle; +drop sequence s2; +create sequence s2 start with 1 + minvalue 1 + maxvalue 100000 + increment by 1 + nocache + nocycle; +drop sequence s2; + +--error ER_SEQUENCE_INVALID +create sequence s2 start with 1 + minvalue 5 + maxvalue 100000 + increment by 1 + nocache + nocycle; + +--error ER_SEQUENCE_INVALID +create sequence s2 start with 1 + minvalue 5 + maxvalue 5 + increment by 1 + nocache + nocycle; + +--error ER_SEQUENCE_INVALID +create sequence s2 start with 1 + minvalue 5 + maxvalue 4 + increment by 1 + nocache + nocycle; + +--error ER_SEQUENCE_INVALID +create sequence s2 start with 1 + minvalue 5 + maxvalue 4 + increment by 0 + nocache + nocycle; + +--echo ########################################### +--echo global read lock prevent query sequence +--echo ########################################### +connection master; +use s_db; +create sequence s_db.s1; +flush table with read lock; +--error ER_CANT_UPDATE_WITH_READLOCK +select nextval(s1); + +unlock tables; + +drop sequence s1; + +--echo ########################################### +--echo session setting +--echo ########################################### +connection master; +use s_db; +create sequence s1; +set session sequence_read_skip_cache=true; +select nextval(s1); +select currval(s1); + +set session sequence_read_skip_cache=false; +select nextval(s1); +select currval(s1); + +drop sequence s1; + + +--echo ########################################### +--echo priv test +--echo ########################################### +connection m_normal_1; +create sequence s_db.s1; +select * from s_db.s1; +create sequence s_db.s2; +drop sequence s_db.s2; + + +connection m_normal_2; +--error ER_TABLEACCESS_DENIED_ERROR +select nextval(s_db.s1); +--error ER_TABLEACCESS_DENIED_ERROR +create sequence s_db.s2; + +connection m_normal_1; +drop sequence s_db.s1; + + +--echo ########################################### +--echo run out sequence value +--echo ########################################### +connection m_normal_1; +use s_db; +create sequence s_t start with 1 cache 2 maxvalue 5; +create table t(id int); +insert into t values(1111); +insert into t select nextval(s_t); +insert into t select nextval(s_t); +insert into t select nextval(s_t); +insert into t select nextval(s_t); +insert into t select nextval(s_t); +--error ER_SEQUENCE_RUN_OUT +insert into t select nextval(s_t); +--error ER_SEQUENCE_RUN_OUT +insert into t select nextval(s_t); +commit; +select * from t; + +connection master; +--sync_slave_with_master + +connection s_normal_3; +use s_db; +select * from t; + +connection m_normal_1; +use s_db; +drop sequence s_t; +drop table t; + +--echo ########################################### +--echo read_only prevent query sequence +--echo ########################################### +connection m_normal_1; +create sequence s_db.s1 nocache; +show global variables like 'read_only'; +select nextval(s_db.s1); + +connection s_normal_3; +show global variables like 'read_only'; + +--error ER_OPTION_PREVENTS_STATEMENT +select nextval(s_db.s1); + +connection m_normal_1; +drop sequence s_db.s1; + + + +--echo ########################################### +--echo update based table +--echo ########################################### +connection m_normal_1; +use s_db; +create sequence s_t start with 1 minvalue 1 maxvalue 20 increment by 1 cache 5 cycle; + +connection master; +--sync_slave_with_master + +connection s_normal_3; +use s_db; +select * from s_t; + + +connection m_normal_1; +select nextval(s_t); + +connection master; +--sync_slave_with_master + +connection s_normal_3; +select * from s_t; + +--echo ------------------------------------------ +--echo master update nextval; +--echo ------------------------------------------ +connection m_normal_1; +select nextval(s_t); +update s_t set nextval= 11; +commit; + +select * from s_t; + +connection master; +--sync_slave_with_master + +--echo ------------------------------------------ +--echo show slave nextval; +--echo ------------------------------------------ +connection s_normal_3; +select * from s_t; + +connection m_normal_1; +set session sequence_read_skip_cache=off; +select nextval(s_t); + +connection master; +--sync_slave_with_master + +connection s_normal_3; +select * from s_t; + + + +--echo ------------------------------------------ +--echo update into invalid sequence +--echo ------------------------------------------ +connection m_normal_1; +select nextval(s_t); +update s_t set nextval= 11,start=10, minvalue=11; +commit; + +create table t_1(id int); +insert into t_1 value(1111); +--error ER_SEQUENCE_INVALID +select nextval(s_t); +--error ER_SEQUENCE_INVALID +insert into t_1 select nextval(s_t); +commit; + +select * from t_1; + +--echo ------------------------------------------ +--echo delete sequence row +--echo ------------------------------------------ +connection m_normal_1; +delete from s_t; +commit; + +--error ER_KEY_NOT_FOUND +select nextval(s_t); + +connection m_normal_1; +drop sequence s_t; +drop table t_1; + + +--echo ########################################### +--echo test transaction context (myisam) +--echo ########################################### + +--echo ------------------------------------------ +--echo transaction table and sequence +--echo normal transaction commit +--echo ------------------------------------------ +connection m_normal_1; +use s_db; +set session sequence_read_skip_cache=off; +create sequence s_1 cache 5; + +create table t_1(id int)engine=myisam; +begin; +insert into t_1 values(1111); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 values(2222); +commit; + +select * from t_1; + +connection master; +--sync_slave_with_master + +connection s_normal_3; +set session sequence_read_skip_cache=off; +use s_db; +select * from t_1; + +--echo ------------------------------------------ +--echo normal transaction rollback +--echo ------------------------------------------ +connection m_normal_1; +begin; +insert into t_1 values(3333); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); +insert into t_1 select nextval(s_1); + +select * from t_1; +rollback; + +select * from t_1; +select nextval(s_1); + +connection master; +--sync_slave_with_master + +connection s_normal_3; +set session sequence_read_skip_cache=off; +use s_db; +select * from t_1; + +connection m_normal_1; +use s_db; +drop sequence s_1; +drop table t_1; + + +--echo ########################################### +--echo close binlog +--echo ########################################### +connection m_normal_1; +use s_db; +create sequence s1 cache 2; +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); + +commit; +select * from s1; + +connection master; +--sync_slave_with_master + +connection slave; +use s_db; +select * from s1; + +--echo ------------------------------------------ +--echo close session binlog. +--echo ------------------------------------------ +connection master; +set session sql_log_bin=off; +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); + +set session sql_log_bin=on; +select * from s1; + +connection master; +--sync_slave_with_master + +connection slave; +use s_db; +select * from s1; + +connection m_normal_1; +use s_db; +drop sequence s1; + +--echo ########################################### +--echo statement binlog +--echo ########################################### + +--echo ------------------------------------------ +--echo set binlog_format=statement +--echo ------------------------------------------ +connection master; +set session sequence_read_skip_cache=off; +set session binlog_format=statement; +show session variables like '%binlog_format%'; +create sequence s1 cache 2; +--error ER_SEQUENCE_BINLOG_FORMAT +select nextval(s1); + +set session binlog_format=row; +select nextval(s1); + +connection master; +--sync_slave_with_master + +connection slave; +use s_db; +select * from s1; +set session sequence_read_skip_cache=off; + +connection m_normal_1; +use s_db; +drop sequence s1; + +--echo ------------------------------------------ +--echo set binlog_format=mixed +--echo ------------------------------------------ +connection master; +set session sequence_read_skip_cache=off; +set session binlog_format=mixed; +show session variables like '%binlog_format%'; +create sequence s1 cache 2; +--error ER_SEQUENCE_BINLOG_FORMAT +select nextval(s1); + +set session binlog_format=row; +select nextval(s1); + +connection master; +--sync_slave_with_master + +connection slave; +use s_db; +select * from s1; +set session sequence_read_skip_cache=off; + +connection m_normal_1; +use s_db; +drop sequence s1; + +--echo ########################################### +--echo test savepoint +--echo ########################################### +connection master; +set session sequence_read_skip_cache=off; +set session binlog_format=row; + +create sequence s1 cache 2; +create table t1(id int)engine=innodb; + +begin; +insert into t1 values(1111); +savepoint sp1; +insert into t1 select nextval(s1); +insert into t1 select nextval(s1); +insert into t1 select nextval(s1); + +insert into t1 values(2222); + +select * from t1; +rollback to sp1; +select * from t1; +select nextval(s1); + +commit; + +drop sequence s1; +drop table t1; + +--echo ########################################### +--echo test proc +--echo ########################################### +connection m_normal_1; +set session sequence_read_skip_cache=off; +use s_db; +create table t(id int)engine=innodb; + +delimiter //; + +create procedure p1() +begin + create sequence s1 cache 2; +end// + +create procedure p2() +begin + insert into t select nextval(s1); + commit; +end// + +delimiter ;// + +call p1(); +call p2(); +call p2(); +call p2(); +call p2(); + +select * from t; + +connection master; +--sync_slave_with_master + +connection slave; +use s_db; +select * from t; + +connection m_normal_1; +drop table t; +drop sequence s1; +drop procedure p1; +drop procedure p2; + + +--echo ########################################### +--echo test trigger +--echo ########################################### +connection m_normal_1; +set session sequence_read_skip_cache=off; +use s_db; +create sequence s1 cache 2; +create table t1(id int)engine=innodb; +create table t2(id int)engine=innodb; + +delimiter //; +CREATE TRIGGER tri_1 + before INSERT ON t2 FOR EACH ROW +BEGIN + INSERT INTO t1 select nextval(s1); +END// +delimiter ;// + +begin; +insert into t2 values(1111); +insert into t2 values(1111); +insert into t2 values(1111); +insert into t2 values(1111); + +select * from t2; +select * from t1; +rollback; +select * from t2; +select * from t1; + +select nextval(s1); + + +drop trigger tri_1; +drop table t1; +drop table t2; +drop sequence s1; + +--echo ########################################### +--echo test value boundary +--echo ########################################### +connection m_normal_1; +use s_db; + + +--echo ------------------------------------------ +--echo round increment by round +--echo ------------------------------------------ +create sequence s1 start with 5 minvalue 2 maxvalue 7 cache 1 cycle; +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +drop sequence s1; + +create sequence s1 start with 5 minvalue 2 maxvalue 7 cache 10 nocycle; +select nextval(s1); +select nextval(s1); +select nextval(s1); +--error ER_SEQUENCE_RUN_OUT +select nextval(s1); +drop sequence s1; + +create sequence s1 start with 2 minvalue 1 maxvalue 3 increment by 3 nocache cycle; +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +drop sequence s1; + +create sequence s1 start with 2 minvalue 1 maxvalue 3 increment by 3 cache 2 nocycle; +select nextval(s1); +--error ER_SEQUENCE_RUN_OUT +select nextval(s1); +drop sequence s1; + + +--echo ------------------------------------------ +--echo beyond ulonglong maxvalue +--echo ------------------------------------------ +create sequence s1 start with 9223372036854775805 minvalue 9223372036854775804 maxvalue 9223372036854775807 cache 1 cycle; +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +drop sequence s1; + +create sequence s1 start with 9223372036854775805 minvalue 9223372036854775804 maxvalue 9223372036854775807 cache 10 cycle; +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +select nextval(s1); +drop sequence s1; + +connection master; +drop database s_db; +drop user normal_1@'%'; +drop user normal_2@'%'; +drop user normal_3@'%'; +drop user normal_4@'%'; + +connection slave; +set global read_only = @start_read_only; + +connection master; +--sync_slave_with_master +--source include/rpl_end.inc diff --git a/mysql-test/suite/sys_vars/r/all_vars.result b/mysql-test/suite/sys_vars/r/all_vars.result index bc1a1801fd55..27d388100e43 100644 --- a/mysql-test/suite/sys_vars/r/all_vars.result +++ b/mysql-test/suite/sys_vars/r/all_vars.result @@ -40,6 +40,7 @@ regexp_stack_limit regexp_time_limit regexp_time_limit resultset_metadata +sequence_read_skip_cache sql_require_primary_key sql_require_primary_key tls_version diff --git a/mysql-test/t/udf.test b/mysql-test/t/udf.test index 138f3162a18a..5fa71443e47e 100644 --- a/mysql-test/t/udf.test +++ b/mysql-test/t/udf.test @@ -26,7 +26,7 @@ eval CREATE FUNCTION myfunc_nonexist RETURNS INTEGER SONAME "$UDF_EXAMPLE_LIB"; --replace_result $UDF_EXAMPLE_LIB UDF_EXAMPLE_LIB eval CREATE FUNCTION myfunc_int RETURNS INTEGER SONAME "$UDF_EXAMPLE_LIB"; --replace_result $UDF_EXAMPLE_LIB UDF_EXAMPLE_LIB -eval CREATE FUNCTION sequence RETURNS INTEGER SONAME "$UDF_EXAMPLE_LIB"; +eval CREATE FUNCTION sequence_alias RETURNS INTEGER SONAME "$UDF_EXAMPLE_LIB"; --replace_result $UDF_EXAMPLE_LIB UDF_EXAMPLE_LIB eval CREATE FUNCTION lookup RETURNS STRING SONAME "$UDF_EXAMPLE_LIB"; --replace_result $UDF_EXAMPLE_LIB UDF_EXAMPLE_LIB @@ -256,7 +256,7 @@ DROP FUNCTION myfunc_double; --error ER_SP_DOES_NOT_EXIST DROP FUNCTION myfunc_nonexist; DROP FUNCTION myfunc_int; -DROP FUNCTION sequence; +DROP FUNCTION sequence_alias; DROP FUNCTION lookup; DROP FUNCTION reverse_lookup; DROP FUNCTION avgcost; @@ -419,19 +419,19 @@ DROP TABLE const_len_bug; # --replace_result $UDF_EXAMPLE_LIB UDF_EXAMPLE_LIB -eval CREATE FUNCTION sequence RETURNS INTEGER SONAME "$UDF_EXAMPLE_LIB"; +eval CREATE FUNCTION sequence_alias RETURNS INTEGER SONAME "$UDF_EXAMPLE_LIB"; CREATE TABLE t1 (a INT); CREATE TABLE t2 (a INT PRIMARY KEY); INSERT INTO t1 VALUES (4),(3),(2),(1); INSERT INTO t2 SELECT * FROM t1; -SELECT sequence() AS seq, a FROM t1 ORDER BY seq ASC; -SELECT sequence() AS seq, a FROM t1 ORDER BY seq DESC; +SELECT sequence_alias() AS seq, a FROM t1 ORDER BY seq ASC; +SELECT sequence_alias() AS seq, a FROM t1 ORDER BY seq DESC; -SELECT * FROM t1 WHERE a = sequence(); -SELECT * FROM t2 WHERE a = sequence(); +SELECT * FROM t1 WHERE a = sequence_alias(); +SELECT * FROM t2 WHERE a = sequence_alias(); -DROP FUNCTION sequence; +DROP FUNCTION sequence_alias; DROP TABLE t1,t2; # diff --git a/mysys/my_handler_errors.h b/mysys/my_handler_errors.h index 0f7b0286eb2c..804deabc8fd2 100644 --- a/mysys/my_handler_errors.h +++ b/mysys/my_handler_errors.h @@ -119,7 +119,11 @@ static const char *handler_error_messages[] = { "Row format changed in storage engine", "Do not wait for lock", "No more room in disk", - "No session temporary tablespace available"}; + "No session temporary tablespace available", + "Sequence has run out", + "Sequence structure or number is invalid", + "Sequence is not yet defined in current session", + "Sequence access failure"}; extern void my_handler_error_register(void); extern void my_handler_error_unregister(void); diff --git a/share/errmsg-utf8.txt b/share/errmsg-utf8.txt index 3da7ac6d9389..65b97c66fd66 100644 --- a/share/errmsg-utf8.txt +++ b/share/errmsg-utf8.txt @@ -18442,6 +18442,23 @@ ER_SSL_MEMORY_INSTRUMENTATION_INIT_FAILED eng "The SSL library function %s failed. This is typically caused by the SSL library already being used. As a result the SSL memory allocation will not be instrumented." bg "Функцията от SSL библиотеката %s върна грешка. Това обикновено е защото SSL библиотеката вече е била използвана. Заради това SSL паметта няма да се инструментира." +ER_SEQUENCE_RUN_OUT + eng "Sequence '%-.192s.%-.192s' has run out." + +ER_SEQUENCE_INVALID + eng "Sequence '%-.192s.%-.192s' structure or number is invalid." + +ER_SEQUENCE_ACCESS_FAILURE + eng "Sequence '%-.192s.%-.192s' access failure." + +ER_SEQUENCE_BINLOG_FORMAT + eng "Sequence requires binlog_format = row" + +ER_SEQUENCE_NOT_DEFINED + eng "Sequence '%-.192s.%-.192s' is not yet defined in current session" + +ER_TABLE_IS_NOT_SEQUENCE + eng "Table '%-.192s.%-.192s' should be sequence" # # End of 8.0 error messages intended to be logged to the server error log. # diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 01e231041386..5ad78258608c 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -503,6 +503,11 @@ SET(SQL_SHARED_SOURCES tztime.cc uniques.cc xa.cc + ha_sequence.cc + item_sequence_func.cc + sequence_common.cc + sequence_transaction.cc + sql_sequence.cc ${MYSQL_SERVER_SUB_COMPONENT_SOURCES} ) @@ -901,6 +906,10 @@ IF(NOT DISABLE_SHARED) ENDIF() ENDIF() +#Sequence engine will be registered as mandatory plugin +MYSQL_ADD_PLUGIN(sequence ha_sequence.cc STORAGE_ENGINE MANDATORY DEFAULT + STATIC_ONLY MODULE_OUTPUT_NAME "sequence") + FOREACH(tool gtar tar git) STRING(TOUPPER ${tool} TOOL) FIND_PROGRAM(${TOOL}_EXECUTABLE ${tool} DOC "path to the executable") diff --git a/sql/dd_table_share.cc b/sql/dd_table_share.cc index 5c411a12e30c..c9c7af6abaa5 100644 --- a/sql/dd_table_share.cc +++ b/sql/dd_table_share.cc @@ -88,6 +88,8 @@ #include "sql/thd_raii.h" #include "typelib.h" +#include "sql/sequence_common.h" // Sequence_property + namespace histograms { class Histogram; } // namespace histograms @@ -601,6 +603,8 @@ static bool fill_share_from_dd(THD *thd, TABLE_SHARE *share, plugin_unlock(NULL, share->db_plugin); share->db_plugin = my_plugin_lock(NULL, &tmp_plugin); + /* Flag the sequence property if it is sequence table */ + share->sequence_property->configure(share->db_plugin); } else { my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), engine_name.str); return true; diff --git a/sql/ha_sequence.cc b/sql/ha_sequence.cc new file mode 100644 index 000000000000..52ae6964b6dc --- /dev/null +++ b/sql/ha_sequence.cc @@ -0,0 +1,1339 @@ +/* Copyright (c) 2000, 2018, Alibaba and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** + @file + + Sequence Engine handler interface and implementation. +*/ + +#include "my_systime.h" +#include "mysql/components/services/psi_mutex_bits.h" //PSI_mutex_key +#include "mysql/components/services/psi_rwlock_bits.h" +#include "mysql/plugin.h" //st_mysql_storage_engine +#include "mysql/psi/mysql_cond.h" //mysql_mutex_init +#include "mysql/psi/mysql_cond.h" +#include "mysql/psi/mysql_memory.h" +#include "mysql/psi/mysql_mutex.h" //mysql_mutex_init +#include "mysql/service_mysql_alloc.h" +#include "sql/handler.h" +#include "sql/mysqld.h" +#include "sql/psi_memory_key.h" +#include "sql/sequence_transaction.h" +#include "sql/sql_update.h" //compare_record + +#include "sql/ha_sequence.h" + + +/** + @addtogroup Sequence Engine + + Implementation of Sequence Engine interface + + @{ +*/ + +#define SEQUENCE_ENABLED_TABLE_FLAGS (HA_FILE_BASED) + +#define SEQUENCE_DISABLED_TABLE_FLAGS \ + (HA_CAN_GEOMETRY | HA_CAN_FULLTEXT | HA_DUPLICATE_POS | HA_CAN_SQL_HANDLER) + +#ifdef HAVE_PSI_INTERFACE +static PSI_mutex_key key_LOCK_sequence_share; +static PSI_mutex_key key_LOCK_sequence_open_shares_hash; +static PSI_cond_key key_COND_sequence_share; +static PSI_memory_key key_memory_sequence_share; + +static PSI_mutex_info sequence_mutexes[] = { + {&key_LOCK_sequence_share, "LOCK_sequence_share", 0, 0, PSI_DOCUMENT_ME}, + {&key_LOCK_sequence_open_shares_hash, "LOCK_sequence_hash", 0, 0, + PSI_DOCUMENT_ME}}; + +static PSI_memory_info sequence_memory[] = { + {&key_memory_sequence_share, "sequence_share", 0, 0, PSI_DOCUMENT_ME}}; + +static PSI_cond_info sequence_conds[] = { + {&key_COND_sequence_share, "sequence_share", 0, 0, PSI_DOCUMENT_ME}}; + +static void init_sequence_psi_keys() { + const char *category = "sql"; + int count; + + count = static_cast(array_elements(sequence_mutexes)); + mysql_mutex_register(category, sequence_mutexes, count); + + count = static_cast(array_elements(sequence_memory)); + mysql_memory_register(category, sequence_memory, count); + + count = static_cast(array_elements(sequence_conds)); + mysql_cond_register(category, sequence_conds, count); +} +#endif /* HAVE_PSI_INTERFACE */ + +/* Global sequence engine handlerton */ +handlerton *sequence_hton; + + +static const char sequence_plugin_author[] = "jianwei.zhao, Aliyun"; +static const char sequence_plugin_name[] = "Sequence"; + +/* Protect sequence_open_shares map */ +static mysql_mutex_t LOCK_sequence_open_shares_hash; +/* Sequence open shares map */ +typedef collation_unordered_map + Sequence_shares_hash; +static Sequence_shares_hash *sequence_shares_hash; + +/* Increment the sequence version */ +static ulonglong sequence_global_version = 0; + +static bool sequence_engine_inited = false; + +static Sequence_share *get_share(const char *name) +{ + Sequence_share *share = NULL; + Sequence_shares_hash::const_iterator it; + DBUG_ENTER("get_share"); + + /** + We will hold the lock until the object creation, if the sequence_share + didn't exist in the map, since the creation has only very low cost. + + Otherwise we should set CREATING flag to release the lock and + load sequence value from table slowly. + */ + mysql_mutex_lock(&LOCK_sequence_open_shares_hash); + + it = sequence_shares_hash->find(std::string(name)); + if (it != sequence_shares_hash->end()) { + share = it->second; + } else { + share = new Sequence_share(); + share->init(name); + share->m_version = sequence_global_version++; + sequence_shares_hash->insert( + std::pair(std::string(name), share)); + } + + if (share) share->m_ref_count++; + + mysql_mutex_unlock(&LOCK_sequence_open_shares_hash); + DBUG_RETURN(share); +} +/** + Close the sequence share, + make sure that sequence handler has been disassociated from it. + + @param[in] share Sequence share object + + @retval void +*/ +static void close_share(Sequence_share *share) { + DBUG_ENTER("close_share"); + + mysql_mutex_lock(&LOCK_sequence_open_shares_hash); +#ifndef DBUG_OFF + Sequence_shares_hash::const_iterator it = + sequence_shares_hash->find(std::string(share->m_name)); + DBUG_ASSERT(it != sequence_shares_hash->end() && it->second == share); +#endif + DBUG_ASSERT(share->m_ref_count > 0); + --share->m_ref_count; + mysql_mutex_unlock(&LOCK_sequence_open_shares_hash); + DBUG_VOID_RETURN; +} +/** + Destroy the sequence_share object. + + @param[in] name Destroy the sequence_share object. + + @retval void +*/ +static void destroy_share(const char *name) { + DBUG_ENTER("destroy_share"); + mysql_mutex_lock(&LOCK_sequence_open_shares_hash); + + Sequence_shares_hash::const_iterator it = + sequence_shares_hash->find(std::string(name)); + + if (it != sequence_shares_hash->end()) { + delete it->second; + sequence_shares_hash->erase(it); + } + mysql_mutex_unlock(&LOCK_sequence_open_shares_hash); + DBUG_VOID_RETURN; +} + +/** + Init all the member variable. + + @param[in] table_name db_name + table_name + + @retval void +*/ +void Sequence_share::init(const char *table_name) { + DBUG_ENTER("Sequence_share::init"); + mysql_mutex_init(key_LOCK_sequence_share, &m_mutex, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_COND_sequence_share, &m_cond); + size_t length = strlen(table_name); + m_name = my_strndup(key_memory_sequence_share, table_name, length, + MYF(MY_FAE | MY_ZEROFILL)); + + bitmap_init(&m_read_set, NULL, SF_END, false); + bitmap_init(&m_write_set, NULL, SF_END, false); + bitmap_set_all(&m_read_set); + bitmap_set_all(&m_write_set); + + m_cache_state = CACHE_STATE_INVALID; + m_initialized = true; + m_cache_end = 0; + m_ref_count = 0; + memset(m_caches, 0, sizeof(m_caches)); + DBUG_VOID_RETURN; +} + +/** + Get sequence share cache field value pointer + + @param[in] field_num The sequence field number + + @retval field pointer +*/ +ulonglong *Sequence_share::get_field_ptr(const Sequence_field field_num) { + DBUG_ENTER("Sequence_share::get_field_ptr"); + DBUG_ASSERT(field_num < SF_END); + DBUG_RETURN(&m_caches[field_num]); +} + +/** + Enter the wait condition until loading complete or error happened. + @param[in] thd User connection + + @retval 0 Success + @retval ~0 Failure +*/ +int Sequence_share::enter_cond(THD *thd) { + int wait_result = 0; + struct timespec abs_timeout; + + mysql_mutex_assert_owner(&m_mutex); + set_timespec(&abs_timeout, thd->variables.lock_wait_timeout); + + while (m_cache_state == CACHE_STATE_LOADING && !thd->is_killed() && + !is_timeout(wait_result)) { + wait_result = mysql_cond_timedwait(&m_cond, &m_mutex, &abs_timeout); + } + + if (m_cache_state == CACHE_STATE_LOADING) { + if (thd->is_killed()) { + thd_set_kill_status(thd); // set my_error + } else if (is_timeout(wait_result)) { + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + } + return HA_ERR_SEQUENCE_ACCESS_FAILURE; + } + return 0; +} +/** + Retrieve the nextval from cache directly. + + @param[out] local_values Used to store into thd->sequence_last_value + + @retval request Cache request result +*/ +Sequence_cache_request Sequence_share::quick_read(ulonglong *local_values) { + ulonglong *nextval_ptr; + ulonglong *currval_ptr; + ulonglong *increment_ptr; + bool last_round; + DBUG_ENTER("Sequence_share::quick_read"); + + mysql_mutex_assert_owner(&m_mutex); + DBUG_ASSERT(m_cache_state != CACHE_STATE_LOADING); + + nextval_ptr = &m_caches[SF_NEXTVAL]; + currval_ptr = &m_caches[SF_CURRVAL]; + increment_ptr = &m_caches[SF_INCREMENT]; + + /* If cache is not valid, need load and flush cache. */ + if (m_cache_state == CACHE_STATE_INVALID) + DBUG_RETURN(CACHE_REQUEST_NEED_LOAD); + + DBUG_ASSERT(m_cache_state == CACHE_STATE_VALID); + + /* If cache_end roll upon maxvalue, then it is last round */ + last_round = (m_caches[SF_MAXVALUE] == m_cache_end); + + if (!last_round && ulonglong(*nextval_ptr) >= m_cache_end) { + DBUG_RETURN(CACHE_REQUEST_ROUND_OUT); + } else if (last_round) { + if (*nextval_ptr > m_cache_end) DBUG_RETURN(CACHE_REQUEST_ROUND_OUT); + } + + /* Retrieve values from cache directly */ + { + DBUG_ASSERT(*nextval_ptr <= m_cache_end); + *currval_ptr = *nextval_ptr; + memcpy(local_values, m_caches, sizeof(m_caches)); + if ((m_cache_end - *nextval_ptr) >= *increment_ptr) + *nextval_ptr += *increment_ptr; + else { + *nextval_ptr = m_cache_end; + invalidate(); + } + } + DBUG_RETURN(CACHE_REQUEST_HIT); +} + +/** + Reload the sequence value cache. + + @param[in] table TABLE object + @param[out] changed Whether values are changed + + @retval 0 Success + @retval ~0 Failure +*/ +int Sequence_share::reload_cache(TABLE *table, bool *changed) { + st_sequence_field_info *field_info; + Field **field; + ulonglong durable[SF_END]; + Sequence_field field_num; + DBUG_ENTER("Sequence_share::reload_cache"); + + /* Read the durable values */ + for (field = table->field, field_info = seq_fields; *field; + field++, field_info++) { + field_num = field_info->field_num; + durable[field_num] = (ulonglong)((*field)->val_int()); + } + + /* If someone update the table directly, need this check again. */ + if (check_sequence_values_valid(durable)) + DBUG_RETURN(HA_ERR_SEQUENCE_INVALID); + + /* Calculate the next round cache values */ + ulonglong begin; + + /* Step 1: overlap the cache using durable values */ + for (field_info = seq_fields; field_info->field_name; field_info++) + m_caches[field_info->field_num] = durable[field_info->field_num]; + + /* Step 2: decide the begin value */ + if (m_caches[SF_NEXTVAL] == 0) { + if (m_caches[SF_ROUND] == 0) + /* Take start value as the begining */ + begin = m_caches[SF_START]; + else + /* Next round from minvalue */ + begin = m_caches[SF_MINVALUE]; + } else if (m_caches[SF_NEXTVAL] == m_caches[SF_MAXVALUE]) + /* Run out value when nocycle */ + DBUG_RETURN(HA_ERR_SEQUENCE_RUN_OUT); + else + begin = m_caches[SF_NEXTVAL]; + + DBUG_ASSERT(begin <= m_caches[SF_MAXVALUE]); + + if (begin > m_caches[SF_MAXVALUE]) { + DBUG_RETURN(HA_ERR_SEQUENCE_INVALID); + } + + /* Step 3: calc the left counter to cache */ + longlong left = (m_caches[SF_MAXVALUE] - begin) / m_caches[SF_INCREMENT] - 1; + + /* The left counter is less than cache size */ + if (left < 0 || ((ulonglong)left) <= m_caches[SF_CACHE]) { + /* If cycle, start again; else will report error! */ + m_cache_end = m_caches[SF_MAXVALUE]; + + if (m_caches[SF_CYCLE] > 0) { + durable[SF_NEXTVAL] = 0; + durable[SF_ROUND]++; + } else + durable[SF_NEXTVAL] = m_caches[SF_MAXVALUE]; + } else { + m_cache_end = begin + (m_caches[SF_CACHE] + 1) * m_caches[SF_INCREMENT]; + durable[SF_NEXTVAL] = m_cache_end; + DBUG_ASSERT(m_cache_end < m_caches[SF_MAXVALUE]); + } + + m_caches[SF_NEXTVAL] = begin; + + /* Step 4: Write back durable values */ + store_record(table, record[1]); + for (field = table->field, field_info = seq_fields; *field; + field++, field_info++) { + (*field)->set_notnull(); + (*field)->store(durable[field_info->field_num], true); + } + *changed = compare_records(table); + +#ifndef DBUG_OFF + fprintf(stderr, + "Sequence will write values: " + "currval %llu " + "nextval %llu " + "minvalue %llu " + "maxvalue %llu " + "start %llu " + "increment %llu " + "cache %llu " + "cycle %llu \n", + durable[SF_CURRVAL], + durable[SF_NEXTVAL], + durable[SF_MINVALUE], + durable[SF_MAXVALUE], + durable[SF_START], + durable[SF_INCREMENT], + durable[SF_CACHE], + durable[SF_CYCLE]); +#endif + DBUG_RETURN(0); +} + +/** + Update the base table and flush the caches. + + @param[in] table Super TABLE object + + @retval 0 Success + @retval ~0 Failure +*/ +int ha_sequence::ha_flush_cache(TABLE *) { + int error = 0; + bool changed; + DBUG_ENTER("ha_sequence::ha_flush_cache"); + DBUG_ASSERT(m_file); + + Bitmap_helper helper(table, m_share); + + if ((error = m_file->ha_rnd_init(true))) goto err; + + if ((error = m_file->ha_rnd_next(table->record[0]))) goto err; + + if ((error = m_share->reload_cache(table, &changed))) goto err; + + if (!error && changed) { + if ((error = m_file->ha_update_row(table->record[1], table->record[0]))) + goto err; + } +err: + m_file->ha_rnd_end(); + DBUG_RETURN(error); +} + +/** + Create sequence handler + + @param[in] sequence_info Sequence create info + @param[in] mem_root thd->mem_root, handler is allocated from + it. + + @retval handler Sequence engine handler object +*/ +handler *get_ha_sequence(Sequence_info *sequence_info, MEM_ROOT *mem_root) { + ha_sequence *ha; + DBUG_ENTER("get_ha_sequence"); + if ((ha = new (mem_root) ha_sequence(sequence_hton, sequence_info))) { + if (ha->initialize_sequence(mem_root)) { + destroy(ha); + ha = nullptr; + } else + ha->init(); + } else { + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), + static_cast(sizeof(ha_sequence))); + } + DBUG_RETURN((handler *)ha); +} + +/** + Sequence base table engine setup. +*/ +bool ha_sequence::setup_base_engine() { + handlerton *hton; + DBUG_ENTER("ha_sequence::setup_base_engine"); + DBUG_ASSERT((table_share && table_share->sequence_property->is_sequence()) || + !table_share); + + if (table_share) { + hton = table_share->sequence_property->db_type(); + m_engine = ha_lock_engine(NULL, hton); + } else { + m_engine = ha_resolve_sequence_base(NULL); + } + if (!m_engine) goto err; + + DBUG_RETURN(FALSE); +err: + clear_base_handler_file(); + DBUG_RETURN(TRUE); +} +/** + Clear the locked sequence base table engine and destroy file handler +*/ +void ha_sequence::clear_base_handler_file() { + DBUG_ENTER("ha_sequence::clear_base_handler_file"); + if (m_engine) { + plugin_unlock(NULL, m_engine); + m_engine = NULL; + } + if (m_file) { + destroy(m_file); + m_file = NULL; + } + DBUG_VOID_RETURN; +} + +/** + Create the base table handler by m_engine. + + @param[in] mem_root Memory space + + @retval false Success + @retval true Failure +*/ +bool ha_sequence::setup_base_handler(MEM_ROOT *mem_root) { + handlerton *hton; + + DBUG_ENTER("ha_sequence::setup_base_handler"); + DBUG_ASSERT(m_engine); + + hton = plugin_data(m_engine); + if (!(m_file = get_new_handler(table_share, false, mem_root, hton))) { + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), + static_cast(sizeof(handler))); + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); +} +/** + Setup the sequence base table engine and base file handler. + + @param[in] name Sequence table name + @param[in] mem_root Memory space + + @retval false success + @retval true failure +*/ +bool ha_sequence::get_from_handler_file(const char *, MEM_ROOT *mem_root) { + DBUG_ENTER("ha_sequence::get_from_handler_file"); + + if (m_file) DBUG_RETURN(FALSE); + + if (setup_base_engine() || setup_base_handler(mem_root)) goto err; + + DBUG_RETURN(FALSE); +err: + clear_base_handler_file(); + DBUG_RETURN(TRUE); +} + +/** + Init the sequence base table engine handler by sequence info + + @param[in] mem_root memory space + + @retval false success + @retval true failure +*/ +bool ha_sequence::new_handler_from_sequence_info(MEM_ROOT *mem_root) { + DBUG_ENTER("ha_sequence::new_handler_from_sequence_info"); + DBUG_ASSERT(m_sequence_info); + + if (!(m_file = get_new_handler(table_share, false, mem_root, + m_sequence_info->base_db_type))) { + my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), + static_cast(sizeof(handler))); + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); +} + +/** + Initialize sequence handler + + @param[in] mem_root memory space + + @retval false success + @retval true failure +*/ +bool ha_sequence::initialize_sequence(MEM_ROOT *mem_root) { + DBUG_ENTER("ha_sequence::initialize_sequence"); + + if (m_sequence_info) { + if (new_handler_from_sequence_info(mem_root)) { + DBUG_RETURN(TRUE); + } + } else if (get_from_handler_file(NULL, mem_root)) { + DBUG_RETURN(TRUE); + } + + DBUG_EXECUTE_IF("sequence_handler_error", { + my_error(ER_SEQUENCE_ACCESS_FAILURE, MYF(0), NULL, NULL); + DBUG_RETURN(TRUE); + }); + + DBUG_RETURN(FALSE); +} + +/** + Sequence handlerton create interface function. + + @param[in] hton sequence hton + @param[in] share TABLE_SHARE object + @param[in] partitioned whether base table is partition table + @param[in] mem_root memory space + + @retval handler sequence handler +*/ +static handler *sequence_create_handler(handlerton *hton, TABLE_SHARE *share, + bool, MEM_ROOT *mem_root) { + DBUG_ENTER("sequence_create_handler"); + ha_sequence *file = new (mem_root) ha_sequence(hton, share); + if (file && file->initialize_sequence(mem_root)) { + destroy(file); + file = nullptr; + } + DBUG_RETURN(file); +} +/** + Initialize the sequence handler member variable. +*/ +void ha_sequence::init_variables() { + DBUG_ENTER("ha_sequence::init_variables"); + m_file = NULL; + m_engine = NULL; + m_sequence_info = NULL; + m_share = NULL; + + start_of_scan = 0; + DBUG_VOID_RETURN; +} + +ha_sequence::ha_sequence(handlerton *hton, TABLE_SHARE *share) + : handler(hton, share) { + init_variables(); +} + +/* Init handler when create sequence */ +ha_sequence::ha_sequence(handlerton *hton, Sequence_info *info) + : handler(hton, 0) { + init_variables(); + m_sequence_info = info; +} + +/** + Unlock the base storage plugin and destroy the handler +*/ +ha_sequence::~ha_sequence() { + if (m_share) { + close_share(m_share); + m_share = NULL; + } + clear_base_handler_file(); +} +/** + Fill values into sequence table fields from iterated local_values + + @param[in] thd User connection + @param[in] table TABLE object + @param[in] local_values Temporoary iterated values + + @retval false Success + @retval true Failure +*/ +bool ha_sequence::fill_into_sequence_fields(THD *thd, TABLE *table, + ulonglong *local_values) { + Sequence_last_value *entry; + st_sequence_field_info *field_info; + Field **field; + DBUG_ENTER("fill_sequence_fields"); + + std::string key(table->s->table_cache_key.str, + table->s->table_cache_key.length); + Sequence_last_value_hash::const_iterator it = + thd->get_sequence_hash()->find(key); + + if (it != thd->get_sequence_hash()->end()) { + entry = it->second; + } else { + entry = new Sequence_last_value(); + entry->set_version(m_share->m_version); + thd->get_sequence_hash()->insert( + std::pair(key, entry)); + } + + Bitmap_helper bitmap_helper(table, m_share); + + for (field = table->field, field_info = seq_fields; *field; + field++, field_info++) { + DBUG_ASSERT(!memcmp(field_info->field_name, (*field)->field_name, + strlen(field_info->field_name))); + + ulonglong value = local_values[field_info->field_num]; + (*field)->set_notnull(); + (*field)->store(value, true); + entry->m_values[field_info->field_num] = value; + } + DBUG_RETURN(false); +} + +/** + Fill values into sequence table fields from thd local Sequence_last_value. + + @param[in] thd User connection + @param[in] table TABLE object + + @retval false Success + @retval true Failure +*/ +bool ha_sequence::fill_sequence_fields_from_thd(THD *thd, TABLE *table) { + Sequence_last_value *entry; + st_sequence_field_info *field_info; + Field **field; + DBUG_ENTER("fill_sequence_fields_from_thd"); + + std::string key(table->s->table_cache_key.str, + table->s->table_cache_key.length); + Sequence_last_value_hash::const_iterator it = + thd->get_sequence_hash()->find(key); + + if (it != thd->get_sequence_hash()->end()) { + entry = it->second; + if (entry->get_version() != m_share->m_version) { + thd->get_sequence_hash()->erase(it); + DBUG_RETURN(true); + } + } else { + DBUG_RETURN(true); + } + + Bitmap_helper bitmap_helper(table, m_share); + + for (field = table->field, field_info = seq_fields; *field; + field++, field_info++) { + DBUG_ASSERT(!memcmp(field_info->field_name, (*field)->field_name, + strlen(field_info->field_name))); + ulonglong value = entry->m_values[field_info->field_num]; + (*field)->set_notnull(); + (*field)->store(value, true); + } + + DBUG_RETURN(false); +} + +/** + Sequence full table scan. + + @param[in] scan + @retval ~0 error number + @retval 0 success +*/ +int ha_sequence::rnd_init(bool scan) { + DBUG_ENTER("ha_sequence::rnd_init"); + DBUG_ASSERT(m_file); + DBUG_ASSERT(m_share); + DBUG_ASSERT(table_share && table); + + start_of_scan = 1; + + /* Inherit the sequence scan mode option. */ + m_scan_mode = table->sequence_scan.get(); + m_iter_mode = Sequence_iter_mode::IT_NON; + + if (m_scan_mode == Sequence_scan_mode::ITERATION_SCAN) + m_iter_mode = sequence_iteration_type(table); + + DBUG_RETURN(m_file->ha_rnd_init(scan)); +} + +/** + Sequence engine main logic. + Embedded into the table scan process. + + Logics: + 1.Skip sequence cache to scan the based table record if + a. update; + b. select_from clause; + + 2.Only scan the first row that controlled by + variable 'start_of_scan' + + 3.Lock strategy + a. Only hold MDL_SHARED_READ if cache hit + b. Hold MDL_SHARE_WRITE, GLOBAL READ LOCK when update, and COMMIT LOCK + when autonomous transaction commit if cache miss + + 4.Transaction + a. begin a new autonomous transaction when updating base table. +*/ +int ha_sequence::rnd_next(uchar *buf) { + int error = 0; + int retry_time = 2; + Sequence_cache_request cache_request; + ulonglong local_values[SF_END]; + DBUG_ENTER("ha_sequence::rnd_next"); + + DBUG_ASSERT(m_file && m_share && ha_thd() && table_share && table); + + if (get_lock_type() == F_WRLCK || + m_scan_mode == Sequence_scan_mode::ORIGINAL_SCAN || + ha_thd()->variables.sequence_read_skip_cache) { + DBUG_RETURN(m_file->ha_rnd_next(buf)); + } + + if (start_of_scan) { + + start_of_scan = 0; + + /** + Get the currval from THD local sequence_last_value directly if only query + currval. + */ + if (m_iter_mode == Sequence_iter_mode::IT_NON_NEXTVAL) { + if (fill_sequence_fields_from_thd(ha_thd(), table)) + DBUG_RETURN(HA_ERR_SEQUENCE_NOT_DEFINED); + else + DBUG_RETURN(0); + } + + DBUG_ASSERT(m_iter_mode == Sequence_iter_mode::IT_NEXTVAL); + + Share_locker_helper share_locker(m_share); + + retry_once: + retry_time--; + /** + Enter the condition: + 1. Wait if other thread is loading the cache. + 2. Report error if timeout. + 3. Return if thd->killed. + */ + if ((error = m_share->enter_cond(ha_thd()))) { + DBUG_RETURN(error); + } + cache_request = m_share->quick_read(local_values); + switch (cache_request) { + case Sequence_cache_request::CACHE_REQUEST_HIT: + goto end; + case Sequence_cache_request::CACHE_REQUEST_ERROR: { + error = HA_ERR_SEQUENCE_ACCESS_FAILURE; + break; + } + + case Sequence_cache_request::CACHE_REQUEST_NEED_LOAD: + case Sequence_cache_request::CACHE_REQUEST_ROUND_OUT: { + if (retry_time > 0) { + error = scroll_sequence(table, cache_request, &share_locker); + share_locker.complete_load(error); + if (error) + break; + else + goto retry_once; + } else { + error = HA_ERR_SEQUENCE_RUN_OUT; + break; + } + } + } /* switch end */ + + /* Here is the switch error result, if success, will goto end. */ + m_share->invalidate(); + DBUG_RETURN(error); + } else + DBUG_RETURN(HA_ERR_END_OF_FILE); /* if (start_of_scan) end */ + +end: + /* Fill the Sequence_last_value object.*/ + if (fill_into_sequence_fields(ha_thd(), table, local_values)) + DBUG_RETURN(HA_ERR_SEQUENCE_ACCESS_FAILURE); + DBUG_RETURN(0); +} + +int ha_sequence::rnd_end() { + DBUG_ENTER("ha_sequence::rnd_end"); + DBUG_ASSERT(m_file && m_share); + DBUG_ASSERT(table_share && table); + DBUG_RETURN(m_file->ha_rnd_end()); +} + +int ha_sequence::rnd_pos(uchar *buf, uchar *pos) { + DBUG_ENTER("ha_sequence::rnd_pos"); + DBUG_ASSERT(m_file); + DBUG_RETURN(m_file->ha_rnd_pos(buf, pos)); +} + +void ha_sequence::position(const uchar *record) { + DBUG_ENTER("ha_sequence::positioin"); + DBUG_ASSERT(m_file); + m_file->position(record); +} + +void ha_sequence::update_create_info(HA_CREATE_INFO *create_info) { + if (m_file) m_file->update_create_info(create_info); +} + +int ha_sequence::info(uint) { + DBUG_ENTER("ha_sequence::info"); + DBUG_RETURN(false); +} + +/** + Add hidden columns and indexes to an InnoDB table definition. + + @param[in,out] dd_table data dictionary cache object + + @retval error number + @retval 0 success +*/ +int ha_sequence::get_extra_columns_and_keys( + const HA_CREATE_INFO *create_info, const List *create_list, + const KEY *key_info, uint key_count, dd::Table *dd_table) { + DBUG_ENTER("ha_sequence::get_extra_columns_and_keys"); + DBUG_RETURN(m_file->get_extra_columns_and_keys( + create_info, create_list, key_info, key_count, dd_table)); +} + +const char *ha_sequence::table_type() const { + DBUG_ENTER("ha_sequence::table_type"); + DBUG_RETURN(sequence_plugin_name); +} + +ulong ha_sequence::index_flags(uint inx, uint part, bool all_parts) const { + DBUG_ENTER("ha_sequence::index_flags"); + DBUG_RETURN(m_file->index_flags(inx, part, all_parts)); +} +/** + Store lock +*/ +THR_LOCK_DATA **ha_sequence::store_lock(THD *thd, THR_LOCK_DATA **to, + enum thr_lock_type lock_type) { + DBUG_ENTER("ha_sequence::store_lock"); + DBUG_RETURN(m_file->store_lock(thd, to, lock_type)); +} +/** + Open the sequence table, release the resource in ~ha_sequence if any error + happened. + + @param[in] name Sequence table name. + @param[in] mode + @param[in] test_if_locked + @param[in] table_def DD table definition + + + @retval 0 Success + @retval ~0 Failure +*/ +int ha_sequence::open(const char *name, int mode, uint test_if_locked, + const dd::Table *table_def) { + int error; + DBUG_ENTER("ha_sequence::open"); + DBUG_ASSERT(table->s == table_share); + error = HA_ERR_INITIALIZATION; + + if (!(m_share = get_share(name))) DBUG_RETURN(error); + + if (get_from_handler_file(name, &table->mem_root)) DBUG_RETURN(error); + + DBUG_ASSERT(m_engine && m_file); + + DBUG_RETURN( + (error = m_file->ha_open(table, name, mode, test_if_locked, table_def))); +} + +/** + Close sequence handler. + We didn't destroy share although the ref_count == 0, + the cached values will be lost if we do that. + + @retval 0 Success + @retval ~0 Failure +*/ +int ha_sequence::close(void) { + DBUG_ENTER("ha_sequence::close"); + close_share(m_share); + m_share = NULL; + DBUG_RETURN(m_file->ha_close()); +} + +ulonglong ha_sequence::table_flags() const { + DBUG_ENTER("ha_sequence::table_flags"); + if (!m_file) { + DBUG_RETURN(SEQUENCE_ENABLED_TABLE_FLAGS); + } + DBUG_RETURN(m_file->ha_table_flags() & + ~(HA_STATS_RECORDS_IS_EXACT | HA_REQUIRE_PRIMARY_KEY)); +} + +/** + Create sequence table. + + @param[in] name Sequence table name. + @param[in] form TABLE object + @param[in] create_info create options + @param[in] table_def dd::Table object that has been created + + @retval 0 success + @retval ~0 failure +*/ +int ha_sequence::create(const char *name, TABLE *form, + HA_CREATE_INFO *create_info, dd::Table *table_def) { + int error; + DBUG_ENTER("ha_sequence::create"); + + if (get_from_handler_file(name, ha_thd()->mem_root)) DBUG_RETURN(TRUE); + + DBUG_ASSERT(m_engine && m_file); + if ((error = m_file->ha_create(name, form, create_info, table_def))) goto err; + + DBUG_RETURN(false); + +err: + m_file->ha_delete_table(name, table_def); + + /* Delete the special file for sequence engine. */ + handler::delete_table(name, table_def); + DBUG_RETURN(error); +} + +static const char *ha_sequence_ext[] = {NullS}; + +/** + Sequence engine special file extension + + @retval String array File extension array +*/ +const char **ha_sequence::bas_ext() const { + DBUG_ENTER("ha_sequence::bas_ext"); + DBUG_RETURN(ha_sequence_ext); +} + +/** + Drop sequence table object + + @param[in] name Sequence table name + @param[in] table_def Table DD object + + @retval 0 Success + @retval ~0 Failure +*/ +int ha_sequence::delete_table(const char *name, const dd::Table *table_def) { + DBUG_ENTER("ha_sequence::delete_table"); + + if (get_from_handler_file(name, ha_thd()->mem_root)) DBUG_RETURN(TRUE); + + destroy_share(name); + DBUG_RETURN(m_file->ha_delete_table(name, table_def)); +} + +/** + Write sequence row. + + @param[in] buf table->record + + @retval 0 Success + @retval ~0 Failure +*/ +int ha_sequence::write_row(uchar *buf) { + int error; + DBUG_ENTER("ha_sequence::write_row"); + DBUG_ASSERT(m_file && m_share); + + Share_locker_helper share_locker(m_share); + Disable_binlog_helper disable_binlog(ha_thd()); + if ((error = m_share->enter_cond(ha_thd()))) DBUG_RETURN(error); + m_share->invalidate(); + error = m_file->ha_write_row(buf); + + DBUG_EXECUTE_IF("sequence_write_error", + { error = HA_ERR_SEQUENCE_ACCESS_FAILURE; }); + + DBUG_RETURN(error); +} + +int ha_sequence::update_row(const uchar *old_data, uchar *new_data) { + int error; + DBUG_ENTER("ha_sequence::update_row"); + DBUG_ASSERT(m_file && m_share); + + /* Binlog will decided by m_file engine. so disable here */ + Share_locker_helper share_locker(m_share); + Disable_binlog_helper disable_binlog(ha_thd()); + if ((error = m_share->enter_cond(ha_thd()))) DBUG_RETURN(error); + m_share->invalidate(); + DBUG_RETURN(m_file->ha_update_row(old_data, new_data)); +} + +int ha_sequence::delete_row(const uchar *buf) { + int error; + DBUG_ENTER("ha_sequence::update_row"); + DBUG_ASSERT(m_file && m_share); + + /* Binlog will decided by m_file engine. so disable here */ + Share_locker_helper share_locker(m_share); + Disable_binlog_helper disable_binlog(ha_thd()); + if ((error = m_share->enter_cond(ha_thd()))) DBUG_RETURN(error); + m_share->invalidate(); + DBUG_RETURN(m_file->ha_delete_row(buf)); +} + +/** + External lock + + @param[in] thd User connection + @param[in] lock_typ Lock type + + @retval 0 Success + @retval ~0 Failure +*/ +int ha_sequence::external_lock(THD *thd, int lock_type) { + DBUG_ENTER("ha_sequence::external_lock"); + DBUG_ASSERT(m_file); + DBUG_RETURN(m_file->ha_external_lock(thd, lock_type)); +} + +/** + Scrolling the sequence cache by update the base table through autonomous + transaction. + + @param[in] table TABLE object + @param[in] state Sequence cache state + @param[in] helper Sequence share locker + + @retval 0 Success + @retval ~0 Failure +*/ +int ha_sequence::scroll_sequence(TABLE *table, + Sequence_cache_request cache_request, + Share_locker_helper *helper) { + DBUG_ENTER("ha_sequence::scroll_sequence"); + DBUG_ASSERT(cache_request == + Sequence_cache_request::CACHE_REQUEST_NEED_LOAD || + cache_request == Sequence_cache_request::CACHE_REQUEST_ROUND_OUT); + DBUG_ASSERT(m_share->m_cache_state != + Sequence_cache_state::CACHE_STATE_LOADING); + helper->loading(); + + /* Sequence transaction do the reload */ + Reload_sequence_cache_ctx ctx(ha_thd(), table_share); + DBUG_RETURN(ctx.reload_sequence_cache(table)); +} + +/** + Rename sequence table name. + + @param[in] from Old name of sequence table + @param[in] to New name of sequence table + @param[in] from_table_def Old dd::Table object + @param[in/out] to_table_def New dd::Table object + + @retval 0 Success + @retval ~0 Failure +*/ +int ha_sequence::rename_table(const char *from, const char *to, + const dd::Table *from_table_def, + dd::Table *to_table_def) { + DBUG_ENTER("ha_sequence::rename_table"); + if (get_from_handler_file(from, ha_thd()->mem_root)) DBUG_RETURN(TRUE); + + destroy_share(from); + DBUG_RETURN(m_file->ha_rename_table(from, to, from_table_def, to_table_def)); +} + +/** + Construtor of Bitmap_helper, backup current read/write bitmap set. +*/ +ha_sequence::Bitmap_helper::Bitmap_helper(TABLE *table, Sequence_share *share) + : m_table(table) { + save_read_set = table->read_set; + save_write_set = table->write_set; + table->read_set = &(share->m_read_set); + table->write_set = &(share->m_write_set); +} + +/** + Destrutor of Bitmap_helper, restore the read/write bitmap set. +*/ +ha_sequence::Bitmap_helper::~Bitmap_helper() { + m_table->read_set = save_read_set; + m_table->write_set = save_write_set; +} + +/** + Report sequence error. +*/ +void ha_sequence::print_error(int error, myf errflag) { + THD *thd = ha_thd(); + char *sequence_db = (char *)"???"; + char *sequence_name = (char *)"???"; + DBUG_ENTER("ha_sequence::print_error"); + + if (table_share) { + sequence_db = table_share->db.str; + sequence_name = table_share->table_name.str; + } + switch (error) { + case HA_ERR_SEQUENCE_INVALID: { + my_error(ER_SEQUENCE_INVALID, MYF(0), sequence_db, sequence_name); + DBUG_VOID_RETURN; + } + case HA_ERR_SEQUENCE_RUN_OUT: { + my_error(ER_SEQUENCE_RUN_OUT, MYF(0), sequence_db, sequence_name); + DBUG_VOID_RETURN; + } + case HA_ERR_SEQUENCE_NOT_DEFINED: { + my_error(ER_SEQUENCE_NOT_DEFINED, MYF(0), sequence_db, sequence_name); + DBUG_VOID_RETURN; + } + /* + We has reported error using my_error, so this unkown error + is used to prevent from repeating error definition + */ + case HA_ERR_SEQUENCE_ACCESS_FAILURE: { + if (thd->is_error()) DBUG_VOID_RETURN; + + my_error(ER_SEQUENCE_ACCESS_FAILURE, MYF(0), sequence_db, sequence_name); + DBUG_VOID_RETURN; + } + } + if (m_file) + m_file->print_error(error, errflag); + else + handler::print_error(error, errflag); + + DBUG_VOID_RETURN; +} + +void ha_sequence::unbind_psi() { + DBUG_ENTER("ha_sequence::unbind_psi"); + handler::unbind_psi(); + + DBUG_ASSERT(m_file != NULL); + m_file->unbind_psi(); + DBUG_VOID_RETURN; +} + +void ha_sequence::rebind_psi() { + DBUG_ENTER("ha_sequence::rebind_psi"); + handler::rebind_psi(); + + DBUG_ASSERT(m_file != NULL); + m_file->rebind_psi(); + DBUG_VOID_RETURN; +} + +/** + Sequence engine end. + + @param[in] p engine handlerton + @param[in] type panic type + + @retval 0 success + @retval >0 failure +*/ +static int sequence_end(handlerton *, + ha_panic_function type __attribute__((unused))) { + DBUG_ENTER("sequence_end"); + if (sequence_engine_inited) { + destroy_hash(sequence_shares_hash); + sequence_shares_hash = NULL; + mysql_mutex_destroy(&LOCK_sequence_open_shares_hash); + } + sequence_engine_inited = false; + DBUG_RETURN(0); +} + +/** + Sequence support the atomic ddl by base engine. + + @param[in] thd User connection +*/ +static void sequence_post_ddl(THD *thd) { + handlerton *hton; + plugin_ref plugin; + DBUG_ENTER("sequence_post_ddl"); + if ((plugin = ha_resolve_sequence_base(NULL)) && + (hton = plugin_data(plugin))) { + hton->post_ddl(thd); + } + if (plugin) plugin_unlock(NULL, plugin); + DBUG_VOID_RETURN; +} +/** + Sequence engine init function. + + @param[in] p engine handlerton + + @retval 0 success + @retval >0 failure +*/ +static int sequence_initialize(void *p) { + handlerton *sequence_hton; + DBUG_ENTER("sequence_initialize"); + +#ifdef HAVE_PSI_INTERFACE + init_sequence_psi_keys(); +#endif + + sequence_hton = (handlerton *)p; + // TODO: functions + + sequence_hton->panic = sequence_end; + sequence_hton->db_type = DB_TYPE_SEQUENCE_DB; + sequence_hton->create = sequence_create_handler; + sequence_hton->post_ddl = sequence_post_ddl; + sequence_hton->flags = HTON_SUPPORTS_ATOMIC_DDL; + mysql_mutex_init(key_LOCK_sequence_open_shares_hash, + &LOCK_sequence_open_shares_hash, MY_MUTEX_INIT_FAST); + sequence_shares_hash = + new Sequence_shares_hash(system_charset_info, key_memory_sequence_share); + + sequence_engine_inited = true; + DBUG_RETURN(0); +} + +/** Sequence storage engine declaration */ +static struct st_mysql_storage_engine sequence_storage_engine = { + MYSQL_HANDLERTON_INTERFACE_VERSION}; + +mysql_declare_plugin(sequence) { + MYSQL_STORAGE_ENGINE_PLUGIN, + &sequence_storage_engine, + sequence_plugin_name, + sequence_plugin_author, + "Sequence Storage Engine Helper", + PLUGIN_LICENSE_GPL, + sequence_initialize, /* Plugin Init */ + NULL, /* Plugin Check uninstall */ + NULL, /* Plugin Deinit */ + 0x0100, /* 1.0 */ + NULL, /* status variables */ + NULL, /* system variables */ + NULL, /* config options */ + 0, /* flags */ +} +mysql_declare_plugin_end; + + +/// @} (end of group Sequence Engine) diff --git a/sql/ha_sequence.h b/sql/ha_sequence.h new file mode 100644 index 000000000000..0789fa642ed5 --- /dev/null +++ b/sql/ha_sequence.h @@ -0,0 +1,567 @@ +/* Copyright (c) 2000, 2018, Alibaba and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef HA_SEQUENCE_INCLUDED +#define HA_SEQUENCE_INCLUDED + +#include "my_bitmap.h" // MY_BITMAP +#include "sql/sql_class.h" + +#include "sql/sequence_common.h" + +class THD; + +/* Global sequence engine handlerton variable, inited when plugin_register */ +extern handlerton *sequence_hton; + +/* Define the field */ +#define SF_CURRVAL Sequence_field::FIELD_NUM_CURRVAL +#define SF_NEXTVAL Sequence_field::FIELD_NUM_NEXTVAL +#define SF_MINVALUE Sequence_field::FIELD_NUM_MINVALUE +#define SF_MAXVALUE Sequence_field::FIELD_NUM_MAXVALUE +#define SF_START Sequence_field::FIELD_NUM_START +#define SF_INCREMENT Sequence_field::FIELD_NUM_INCREMENT +#define SF_CACHE Sequence_field::FIELD_NUM_CACHE +#define SF_CYCLE Sequence_field::FIELD_NUM_CYCLE +#define SF_ROUND Sequence_field::FIELD_NUM_ROUND +#define SF_END Sequence_field::FIELD_NUM_END + +/** + The sequence caches class definition, that's allowed to be accessed + simultaneously while protected by mutex. +*/ +class Sequence_share { + public: + /** + Cache data state. + + 1) Retrieve the data from cache if cache is valid. + 2) Need to reload the data from base table if cache is invalid. + 3) Loading represent that some thread is loading data, others should wait. + */ + enum Cache_state { + CACHE_STATE_INVALID, + CACHE_STATE_VALID, + CACHE_STATE_LOADING + }; + + /** + Cache request result. + + 1) Fill data from cache if cache hit + 2) Reload data if cache has run out + 3) Report error if cache has run out and DEF didn't support cycle. + 4) System error. + */ + enum Cache_request { + CACHE_REQUEST_HIT, + CACHE_REQUEST_NEED_LOAD, + CACHE_REQUEST_ROUND_OUT, + CACHE_REQUEST_ERROR + }; + + Sequence_share() {} + + ~Sequence_share() { + DBUG_ENTER("~Sequence_share"); + DBUG_ASSERT(m_ref_count == 0); + mysql_mutex_destroy(&m_mutex); + mysql_cond_destroy(&m_cond); + if (m_name) { + my_free((char *)m_name); + m_name = NULL; + } + bitmap_free(&m_read_set); + bitmap_free(&m_write_set); + m_initialized = false; + DBUG_VOID_RETURN; + } + /** + Init all the member variable. + + @param[in] table_name db_name + table_name + + @retval void + */ + void init(const char *table_name); + + /** + Get sequence share cache field value pointer + + @param[in] field_num The sequence field number + + @retval field pointer + */ + ulonglong *get_field_ptr(const Sequence_field field_num); + + /** + Reload the sequence value cache. + + @param[in] table TABLE object + @param[out] changed Whether values are changed + + @retval 0 Success + @retval ~0 Failure + */ + int reload_cache(TABLE *table, bool *changed); + + /** + Retrieve the nextval from cache directly. + + @param[out] local_values Used to store into thd->sequence_last_value + + @retval request Cache request result + */ + Cache_request quick_read(ulonglong *local_values); + /** + Validate cache. + */ + void validate() { + mysql_mutex_assert_owner(&m_mutex); + m_cache_state = CACHE_STATE_VALID; + mysql_cond_broadcast(&m_cond); + } + /** + Invalidate cache. + */ + void invalidate() { + mysql_mutex_assert_owner(&m_mutex); + m_cache_state = CACHE_STATE_INVALID; + mysql_cond_broadcast(&m_cond); + } + + /* Broadcast the condition if loading completed or updating happened. */ + void set_state(Cache_state state) { + mysql_mutex_assert_owner(&m_mutex); + m_cache_state = state; + if (m_cache_state == CACHE_STATE_INVALID || + m_cache_state == CACHE_STATE_VALID) + mysql_cond_broadcast(&m_cond); + } + /** + Enter the wait condition until loading complete or error happened. + @param[in] thd User connection + + @retval 0 Success + @retval ~0 Failure + */ + int enter_cond(THD *thd); + /** + In order to invalid the THD sequence when sequence is dropped + or altered + */ + ulonglong m_version; + + mysql_mutex_t m_mutex; + mysql_cond_t m_cond; + + /* Protected by m_mutex */ + Cache_state m_cache_state; + + /* Only changed when get_share or close_share, so didn't need m_mutex */ + uint m_ref_count; + bool m_initialized; + + /* All setted read/write set. */ + MY_BITMAP m_read_set; + MY_BITMAP m_write_set; + + /* db_name + table_name */ + const char *m_name; + + private: + /* Protected by m_mutex */ + ulonglong m_caches[Sequence_field::FIELD_NUM_END]; + ulonglong m_cache_end; +}; + +typedef Sequence_share::Cache_state Sequence_cache_state; +typedef Sequence_share::Cache_request Sequence_cache_request; + +/** + Disable binlog generation helper class +*/ +class Disable_binlog_helper { + public: + explicit Disable_binlog_helper(THD *thd) : m_thd(thd) { + m_saved_options = m_thd->variables.option_bits; + m_thd->variables.option_bits &= ~OPTION_BIN_LOG; + } + + ~Disable_binlog_helper() { m_thd->variables.option_bits = m_saved_options; } + + private: + THD *m_thd; + ulonglong m_saved_options; +}; +/** + Sequence engine handler + + @Note + Sequence engine is only logical engine, which didn't store any real data. + The sequence values are stored into the based-table whose engine is InnoDB. + + @Rules + Sequence_share is used to cache values that's consistent with sequence + defined: + + 1. If hit cache, it can query back sequence nextval directly instead of + scanning base-table. + 2. When run out of the caches, sequence engine will lanuch autonomous + transaction to update base-table, and get the new value. + 3. Invalid the caches if any update on base-table. +*/ +class ha_sequence : public handler { + public: + /** + Sequence share object mutex helper class + */ + class Share_locker_helper { + public: + explicit Share_locker_helper(Sequence_share *share) : mm_share(share) { + mysql_mutex_lock(&mm_share->m_mutex); + m_hold_mutex = true; + } + + ~Share_locker_helper() { + if (m_hold_mutex) mysql_mutex_unlock(&mm_share->m_mutex); + } + + void release() { + DBUG_ASSERT(m_hold_mutex); + mysql_mutex_unlock(&mm_share->m_mutex); + m_hold_mutex = false; + } + + void loading() { + DBUG_ASSERT(m_hold_mutex); + mm_share->set_state(Sequence_cache_state::CACHE_STATE_LOADING); + release(); + } + + void complete_load(int error) { + DBUG_ASSERT(!m_hold_mutex); + lock(); + if (error) + mm_share->invalidate(); + else + mm_share->validate(); + } + + void lock() { + DBUG_ASSERT(!m_hold_mutex); + mysql_mutex_lock(&mm_share->m_mutex); + m_hold_mutex = true; + } + + private: + Sequence_share *mm_share; + bool m_hold_mutex; + }; + + /** + TABLE read/write bitmap set helper, since maybe update while query nextval. + */ + class Bitmap_helper { + public: + explicit Bitmap_helper(TABLE *table, Sequence_share *share); + + ~Bitmap_helper(); + + private: + TABLE *m_table; + MY_BITMAP *save_read_set; + MY_BITMAP *save_write_set; + }; + + ha_sequence(handlerton *hton, TABLE_SHARE *share); + + /* Init handler when CREATE SEQUENCE */ + ha_sequence(handlerton *hton, Sequence_info *info); + + /** + Initialize sequence handler + + @param[in] mem_root memory space + + @retval false success + @retval true failure + */ + bool initialize_sequence(MEM_ROOT *mem_root); + + /** + Initialize the sequence handler member variable. + */ + void init_variables(); + + /** + Sequence base table engine setup. + */ + bool setup_base_engine(); + + /** + Create the base table handler by m_engine. + + @param[in] mem_root Memory space + + @retval false Success + @retval true Failure + */ + bool setup_base_handler(MEM_ROOT *mem_root); + + /** + Clear the locked sequence base table engine and destroy file handler + */ + void clear_base_handler_file(); + + /** + Setup the sequence base table engine and base file handler. + + @param[in] name Sequence table name + @param[in] mem_root Memory space + + @retval false success + @retval true failure + */ + bool get_from_handler_file(const char *, MEM_ROOT *mem_root); + + /** + Init the sequence base table engine handler by sequence info + + @param[in] mem_root memory space + + @retval false success + @retval true failure + */ + bool new_handler_from_sequence_info(MEM_ROOT *mem_root); + + /** + Unlock the base storage plugin and destroy the handler + */ + virtual ~ha_sequence(); + + /* virtual function */ + virtual int rnd_init(bool scan); + virtual int rnd_next(uchar *buf); + int rnd_end(); + virtual int rnd_pos(uchar *buf, uchar *pos); + virtual void position(const uchar *record); + + /** Store lock */ + virtual THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, + enum thr_lock_type lock_type); + + /** + Open the sequence table, release the resource in ~ha_sequence if any error + happened. + + @param[in] name Sequence table name. + @param[in] mode + @param[in] test_if_locked + @param[in] table_def DD table definition + + + @retval 0 Success + @retval ~0 Failure + */ + virtual int open(const char *name, int mode, uint test_if_locked, + const dd::Table *); + + /** + Close sequence handler. + We didn't destroy share although the ref_count == 0, + the cached values will be lost if we do that. + + @retval 0 Success + @retval ~0 Failure + */ + virtual int close(void); + + /** Inherit base table handler function implementation */ + virtual Table_flags table_flags() const; + virtual int info(uint); + virtual const char *table_type() const; + virtual ulong index_flags(uint inx, uint part, bool all_parts) const; + + virtual void update_create_info(HA_CREATE_INFO *create_info); + + /** + Add hidden columns and indexes to an InnoDB table definition. + + @param[in,out] dd_table data dictionary cache object + + @retval error number + @retval 0 success + */ + virtual int get_extra_columns_and_keys(const HA_CREATE_INFO *create_info, + const List *create_list, + const KEY *key_info, uint key_count, + dd::Table *dd_table); + /** + Create sequence table. + + @param[in] name Sequence table name. + @param[in] form TABLE object + @param[in] create_info create options + @param[in] table_def dd::Table object that has been created + + @retval 0 success + @retval ~0 failure + */ + virtual int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info, + dd::Table *table_def); + /** + Sequence engine special file extension + + @retval String array File extension array + */ + virtual const char **bas_ext() const; + + /** + Drop sequence table object + + @param[in] name Sequence table name + @param[in] table_def Table DD object + + @retval 0 Success + @retval ~0 Failure + */ + int delete_table(const char *name, const dd::Table *); + + /** + Write sequence row. + + @param[in] buf table->record + + @retval 0 Success + @retval ~0 Failure + */ + int write_row(uchar *buf); + int update_row(const uchar *old_data, uchar *new_data); + int delete_row(const uchar *buf); + /** + External lock + + @param[in] thd User connection + @param[in] lock_typ Lock type + + @retval 0 Success + @retval ~0 Failure + */ + int external_lock(THD *thd, int lock_type); + + /** + Scrolling the sequence cache by update the base table through autonomous + transaction. + + @param[in] table TABLE object + @param[in] request Sequence cache request + @param[in] helper Sequence share locker + + @retval 0 Success + @retval ~0 Failure + */ + int scroll_sequence(TABLE *table, Sequence_cache_request request, + Share_locker_helper *helper); + + /** + Rename sequence table name. + + @param[in] from Old name of sequence table + @param[in] to New name of sequence table + @param[in] from_table_def Old dd::Table object + @param[in/out] to_table_def New dd::Table object + + @retval 0 Success + @retval ~0 Failure + */ + int rename_table(const char *from, const char *to, const dd::Table *, + dd::Table *); + /** + Report sequence error. + */ + void print_error(int error, myf errflag); + + /** + Bind the table/handler thread to track table i/o. + */ + virtual void unbind_psi(); + virtual void rebind_psi(); + + /** + Update the base table and flush the caches. + + @param[in] table Super TABLE object + + @retval 0 Success + @retval ~0 Failure + */ + virtual int ha_flush_cache(TABLE *); + + /** + Fill values into sequence table fields from iterated local_values + + @param[in] thd User connection + @param[in] table TABLE object + @param[in] local_values Temporoary iterated values + + @retval false Success + @retval true Failure + */ + bool fill_into_sequence_fields(THD *thd, TABLE *table, + ulonglong *local_values); + + /** + Fill values int sequence table fields from thd local Sequence_last_value. + + @param[in] thd User connection + @param[in] table TABLE object + + @retval false Success + @retval true Failure + */ + bool fill_sequence_fields_from_thd(THD *thd, TABLE *table); + + private: + handler *m_file; + plugin_ref m_engine; + Sequence_info *m_sequence_info; + Sequence_share *m_share; + ulong start_of_scan; + + Sequence_scan_mode m_scan_mode; + Sequence_iter_mode m_iter_mode; +}; + +/** + Create sequence handler + + @param[in] sequence_info Sequence create info + @param[in] mem_root thd->mem_root, handler is allocated from + it. + + @retval handler Sequence engine handler object +*/ +extern handler *get_ha_sequence(Sequence_info *sequence_info, + MEM_ROOT *mem_root); + +#endif /* HA_SEQUENCE_INCLUDED */ diff --git a/sql/handler.cc b/sql/handler.cc index 3c1332ecde39..83c3e82e2c96 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -87,6 +87,7 @@ #include "sql/derror.h" // ER_DEFAULT #include "sql/error_handler.h" // Internal_error_handler #include "sql/field.h" +#include "sql/ha_sequence.h" #include "sql/item.h" #include "sql/lock.h" // MYSQL_LOCK #include "sql/log.h" @@ -842,6 +843,8 @@ int ha_initialize_handlerton(st_plugin_int *plugin) { case DB_TYPE_INNODB: innodb_hton = hton; break; + case DB_TYPE_SEQUENCE_DB: + sequence_hton = hton; default: break; }; diff --git a/sql/handler.h b/sql/handler.h index 1c193f9e7b59..b0d47422eb64 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -80,6 +80,8 @@ class handler; class partition_info; struct System_status_var; +class Sequence_info; + namespace dd { class Properties; } // namespace dd @@ -621,6 +623,7 @@ enum legacy_db_type { DB_TYPE_PERFORMANCE_SCHEMA, DB_TYPE_TEMPTABLE, DB_TYPE_FIRST_DYNAMIC = 42, + DB_TYPE_SEQUENCE_DB, DB_TYPE_DEFAULT = 127 // Must be last }; @@ -2171,6 +2174,9 @@ struct HA_CREATE_INFO { void init_create_options_from_share(const TABLE_SHARE *share, uint used_fields); + + /* Sequence lex info when CREATE SQUENCE */ + Sequence_info *sequence_info; }; /** @@ -5781,6 +5787,9 @@ class handler { void unlock_shared_ha_data(); friend class DsMrr_impl; + + public: + virtual int ha_flush_cache(TABLE *) { return HA_ERR_WRONG_COMMAND; } }; /** diff --git a/sql/item_sequence_func.cc b/sql/item_sequence_func.cc new file mode 100644 index 000000000000..6ab8e661bfdc --- /dev/null +++ b/sql/item_sequence_func.cc @@ -0,0 +1,108 @@ +/* Copyright (c) 2000, 2017, Alibaba and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** + @file + + Implementation of SEQUENCE NEXTVAL() AND CURRVAL() function. + + Usage Like: + 'SELECT NEXTVAL(s1)' + 'SELECT CURRVAL(s1)' +*/ + +#include "sql/item_sequence_func.h" +#include "sql/sql_sequence.h" + +/** + @addtogroup Sequence Engine + + Sequence Engine native function implementation. + + @{ +*/ + +/** + NEXTVAL() function implementation. +*/ +longlong Item_func_nextval::val_int() { + ulonglong value; + int error; + TABLE *table = table_list->table; + DBUG_ENTER("Item_func_nextval::val_int"); + DBUG_ASSERT(table->file); + + bitmap_set_bit(table->read_set, Sequence_field::FIELD_NUM_NEXTVAL); + + if (table->file->ha_rnd_init(1)) + goto err; + else { + if ((error = table->file->ha_rnd_next(table->record[0]))) { + table->file->print_error(error, MYF(0)); + table->file->ha_rnd_end(); + goto err; + } + table->file->ha_rnd_end(); + + value = table->field[Sequence_field::FIELD_NUM_NEXTVAL]->val_int(); + null_value = 0; + DBUG_RETURN(value); + } +err: + null_value = 1; + DBUG_RETURN(0); +} + +/** + CURRVAL() function implementation. +*/ +longlong Item_func_currval::val_int() { + ulonglong value; + int error; + TABLE *table = table_list->table; + DBUG_ENTER("Item_func_currval::val_int"); + DBUG_ASSERT(table->file); + + bitmap_set_bit(table->read_set, Sequence_field::FIELD_NUM_CURRVAL); + + if (table->file->ha_rnd_init(1)) + goto err; + else { + if ((error = table->file->ha_rnd_next(table->record[0]))) { + table->file->print_error(error, MYF(0)); + table->file->ha_rnd_end(); + goto err; + } + table->file->ha_rnd_end(); + + value = table->field[Sequence_field::FIELD_NUM_CURRVAL]->val_int(); + null_value = 0; + DBUG_RETURN(value); + } +err: + null_value = 1; + DBUG_RETURN(0); +} + + +/// @} (end of group Sequence Engine) + diff --git a/sql/item_sequence_func.h b/sql/item_sequence_func.h new file mode 100644 index 000000000000..98aee60d9a60 --- /dev/null +++ b/sql/item_sequence_func.h @@ -0,0 +1,74 @@ +/* Copyright (c) 2000, 2017, Alibaba and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef SQL_ITEM_SEQUENCE_FUNC_INCLUDED +#define SQL_ITEM_SEQUENCE_FUNC_INCLUDED + +#include "sql/item_func.h" + +/** + Implementation of sequence function: NEXTVAL() +*/ +class Item_func_nextval : public Item_int_func { + protected: + THD *m_thd; + TABLE_LIST *table_list; + + public: + Item_func_nextval(THD *thd, TABLE_LIST *table) + : Item_int_func(), m_thd(thd), table_list(table) {} + + longlong val_int(); + const char *func_name() const { return "nextval"; } + + void fix_length_and_dec() { + unsigned_flag = 1; + max_length = MAX_BIGINT_WIDTH; + maybe_null = 1; + } + bool const_item() const { return 0; } +}; + +/** + Implementation of sequence function: CURRVAL() +*/ +class Item_func_currval : public Item_int_func { + protected: + THD *m_thd; + TABLE_LIST *table_list; + + public: + Item_func_currval(THD *thd, TABLE_LIST *table) + : Item_int_func(), m_thd(thd), table_list(table) {} + + longlong val_int(); + const char *func_name() const { return "currval"; } + void fix_length_and_dec() { + unsigned_flag = 1; + max_length = MAX_BIGINT_WIDTH; + maybe_null = 1; + } + + bool const_item() const { return 0; } +}; + +#endif diff --git a/sql/lex.h b/sql/lex.h index ea45ba350ac8..46e691f95188 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -172,8 +172,10 @@ static const SYMBOL symbols[] = { {SYM("CURRENT_TIME", CURTIME)}, {SYM("CURRENT_TIMESTAMP", NOW_SYM)}, {SYM("CURRENT_USER", CURRENT_USER)}, + {SYM("CURRVAL", CURRVAL_SYM)}, {SYM("CURSOR", CURSOR_SYM)}, {SYM("CURSOR_NAME", CURSOR_NAME_SYM)}, + {SYM("CYCLE", CYCLE_SYM)}, {SYM("DATA", DATA_SYM)}, {SYM("DATABASE", DATABASE)}, {SYM("DATABASES", DATABASES)}, @@ -305,6 +307,7 @@ static const SYMBOL symbols[] = { {SYM("IGNORE_SERVER_IDS", IGNORE_SERVER_IDS_SYM)}, {SYM("IMPORT", IMPORT)}, {SYM("IN", IN_SYM)}, + {SYM("INCREMENT", INCREMENT_SYM)}, {SYM("INDEX", INDEX_SYM)}, {SYM("INDEXES", INDEXES)}, {SYM("INFILE", INFILE)}, @@ -419,6 +422,7 @@ static const SYMBOL symbols[] = { {SYM("MINUTE", MINUTE_SYM)}, {SYM("MINUTE_MICROSECOND", MINUTE_MICROSECOND_SYM)}, {SYM("MINUTE_SECOND", MINUTE_SECOND_SYM)}, + {SYM("MINVALUE", MINVALUE_SYM)}, {SYM("MIN_ROWS", MIN_ROWS)}, {SYM("MOD", MOD_SYM)}, {SYM("MODE", MODE_SYM)}, @@ -441,7 +445,10 @@ static const SYMBOL symbols[] = { {SYM("NEVER", NEVER_SYM)}, {SYM("NEW", NEW_SYM)}, {SYM("NEXT", NEXT_SYM)}, + {SYM("NEXTVAL", NEXTVAL_SYM)}, {SYM("NO", NO_SYM)}, + {SYM("NOCACHE", NOCACHE_SYM)}, + {SYM("NOCYCLE", NOCYCLE_SYM)}, {SYM("NO_WAIT", NO_WAIT_SYM)}, {SYM("NOWAIT", NOWAIT_SYM)}, {SYM("NODEGROUP", NODEGROUP_SYM)}, @@ -592,6 +599,7 @@ static const SYMBOL symbols[] = { {SYM("SECURITY", SECURITY_SYM)}, {SYM("SENSITIVE", SENSITIVE_SYM)}, {SYM("SEPARATOR", SEPARATOR_SYM)}, + {SYM("SEQUENCE", SEQUENCE_SYM)}, {SYM("SERIAL", SERIAL_SYM)}, {SYM("SERIALIZABLE", SERIALIZABLE_SYM)}, {SYM("SESSION", SESSION_SYM)}, diff --git a/sql/mdl.cc b/sql/mdl.cc index fdb401f3c092..9f7d5855d7fe 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -4177,6 +4177,24 @@ void MDL_context::release_locks_stored_before(enum_mdl_duration duration, DBUG_VOID_RETURN; } +void MDL_context::release_autonomous_transactional_locks() { + MDL_ticket *ticket; + enum_mdl_duration duration; + DBUG_ENTER("MDL_context::release_autonomous_transactional_locks"); + duration = MDL_TRANSACTION; + + MDL_ticket_store::List_iterator it = m_ticket_store.list_iterator(duration); + + if (m_ticket_store.is_empty(duration)) { + DBUG_VOID_RETURN; + } + + while ((ticket = it++)) { + if (ticket->is_autonomous()) release_lock(duration, ticket); + } + DBUG_VOID_RETURN; +} + /** Release all explicit locks in the context which correspond to the same name/object as this lock request. @@ -4445,6 +4463,7 @@ void MDL_context::release_transactional_locks() { void MDL_context::release_statement_locks() { DBUG_ENTER("MDL_context::release_transactional_locks"); release_locks_stored_before(MDL_STATEMENT, NULL); + release_autonomous_transactional_locks(); DBUG_VOID_RETURN; } diff --git a/sql/mdl.h b/sql/mdl.h index 58436fe035e1..31afa79215ca 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -914,7 +914,8 @@ class MDL_ticket : public MDL_wait_for_subgraph { m_lock(NULL), m_is_fast_path(false), m_hton_notified(false), - m_psi(NULL) { + m_psi(NULL), + m_autonomous(false) { } virtual ~MDL_ticket() { DBUG_ASSERT(m_psi == NULL); } @@ -967,6 +968,13 @@ class MDL_ticket : public MDL_wait_for_subgraph { private: MDL_ticket(const MDL_ticket &); /* not implemented */ MDL_ticket &operator=(const MDL_ticket &); /* not implemented */ + + public: + bool is_autonomous() { return m_autonomous; } + void set_autonomous(bool type) { m_autonomous = type; } + + private: + bool m_autonomous; }; /** @@ -1347,6 +1355,8 @@ class MDL_context { void release_transactional_locks(); void rollback_to_savepoint(const MDL_savepoint &mdl_savepoint); + void release_autonomous_transactional_locks(); + MDL_context_owner *get_owner() const { return m_owner; } /** @pre Only valid if we started waiting for lock. */ diff --git a/sql/parse_tree_nodes.h b/sql/parse_tree_nodes.h index 7816c2b8e1a2..9e17d70c184d 100644 --- a/sql/parse_tree_nodes.h +++ b/sql/parse_tree_nodes.h @@ -89,6 +89,7 @@ class PT_subquery; class PT_type; class Sql_cmd; struct MEM_ROOT; +class Sequence_info; /** @defgroup ptn Parse tree nodes @@ -200,11 +201,13 @@ struct Table_ddl_parse_context final : public Parse_context { : Parse_context(thd, select), create_info(thd->lex->create_info), alter_info(alter_info), - key_create_info(&thd->lex->key_create_info) {} + key_create_info(&thd->lex->key_create_info), + sequence_info(thd->lex->sequence_info) {} HA_CREATE_INFO *const create_info; Alter_info *const alter_info; KEY_CREATE_INFO *const key_create_info; + Sequence_info *const sequence_info; }; /** @@ -3272,7 +3275,8 @@ class PT_column_def : public PT_table_element { @ingroup ptn_create_table */ -class PT_create_table_stmt final : public PT_table_ddl_stmt_base { +class PT_create_table_stmt : public PT_table_ddl_stmt_base { + protected: bool is_temporary; bool only_if_not_exists; Table_ident *table_name; diff --git a/sql/sequence_common.cc b/sql/sequence_common.cc new file mode 100644 index 000000000000..958abd89b052 --- /dev/null +++ b/sql/sequence_common.cc @@ -0,0 +1,312 @@ +/* Copyright (c) 2000, 2017, Alibaba and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** + @file + + Implementation of SEQUENCE shared structure or function. +*/ + +#include "mysql_com.h" // NOT NULL +#include "mysqld_error.h" // ER_SEQUENCE_INVALID +#include "sql/field.h" // Create_field +#include "sql/handler.h" // DB_TYPE_SEQUENCE_DB +#include "sql/sql_alter.h" // Alter_info +#include "sql/sql_plugin.h" // plugin_unlock +#include "sql/table.h" // bitmap + +#include "sql/sequence_common.h" + + +/** + @addtogroup Sequence Engine + + Sequence Engine shared structure or function implementation. + + @{ +*/ + +PSI_memory_key key_memory_sequence_last_value; + +st_sequence_field_info seq_fields[] = { + {"currval", + "21", + Sequence_field::FIELD_NUM_CURRVAL, + MYSQL_TYPE_LONGLONG, + {C_STRING_WITH_LEN("current value")}}, + {"nextval", + "21", + Sequence_field::FIELD_NUM_NEXTVAL, + MYSQL_TYPE_LONGLONG, + {C_STRING_WITH_LEN("next value")}}, + {"minvalue", + "21", + Sequence_field::FIELD_NUM_MINVALUE, + MYSQL_TYPE_LONGLONG, + {C_STRING_WITH_LEN("min value")}}, + {"maxvalue", + "21", + Sequence_field::FIELD_NUM_MAXVALUE, + MYSQL_TYPE_LONGLONG, + {C_STRING_WITH_LEN("max value")}}, + {"start", + "21", + Sequence_field::FIELD_NUM_START, + MYSQL_TYPE_LONGLONG, + {C_STRING_WITH_LEN("start value")}}, + {"increment", + "21", + Sequence_field::FIELD_NUM_INCREMENT, + MYSQL_TYPE_LONGLONG, + {C_STRING_WITH_LEN("increment value")}}, + {"cache", + "21", + Sequence_field::FIELD_NUM_CACHE, + MYSQL_TYPE_LONGLONG, + {C_STRING_WITH_LEN("cache size")}}, + {"cycle", + "21", + Sequence_field::FIELD_NUM_CYCLE, + MYSQL_TYPE_LONGLONG, + {C_STRING_WITH_LEN("cycle state")}}, + {"round", + "21", + Sequence_field::FIELD_NUM_ROUND, + MYSQL_TYPE_LONGLONG, + {C_STRING_WITH_LEN("already how many round")}}, + {NULL, + NULL, + Sequence_field::FIELD_NUM_END, + MYSQL_TYPE_LONGLONG, + {C_STRING_WITH_LEN("")}}}; +/** + Global variables for sequence engine and sequence base engine, in order to + get the engine plugin through these engine name. +*/ +const LEX_STRING SEQUENCE_ENGINE_NAME = {C_STRING_WITH_LEN("Sequence")}; +const LEX_STRING SEQUENCE_BASE_ENGINE_NAME = {C_STRING_WITH_LEN("InnoDB")}; + +/** + Resolve the sequence engine plugin. + + @param[in] thd user connection + + @retval plugin_ref sequence engine plugin. +*/ +plugin_ref ha_resolve_sequence(const THD *thd) { + return ha_resolve_by_name(const_cast(thd), &SEQUENCE_ENGINE_NAME, + false); +} + +/** + Resolve the sequence base engine plugin. + + @param[in] thd user connection + + @retval plugin_ref sequence base engine plugin. +*/ +plugin_ref ha_resolve_sequence_base(const THD *thd) { + return ha_resolve_by_name(const_cast(thd), &SEQUENCE_BASE_ENGINE_NAME, + false); +} + +/** + Assign initial default values of sequence fields + + @retval void +*/ +void Sequence_info::init_default() { + DBUG_ENTER("Sequence_info::init_default"); + values[FIELD_NUM_CURRVAL] = 0; + values[FIELD_NUM_NEXTVAL] = 0; + values[FIELD_NUM_MINVALUE] = 1; + values[FIELD_NUM_MAXVALUE] = ULLONG_MAX; + values[FIELD_NUM_START] = 1; + values[FIELD_NUM_INCREMENT] = 1; + values[FIELD_NUM_CACHE] = 10000; + values[FIELD_NUM_CYCLE] = 0; + values[FIELD_NUM_ROUND] = 0; + + base_db_type = NULL; + db = NULL; + table_name = NULL; + + DBUG_VOID_RETURN; +} + +/** + Assign the initial values for all sequence fields +*/ +Sequence_info::Sequence_info() { + init_default(); +} +/** + Sequence field setting function + + @param[in] field_num Sequence field number + @param[in] value Sequence field value + + @retval void +*/ +void Sequence_info::init_value(const Fields field_num, const ulonglong value) { + DBUG_ENTER("Sequence_info::init_value"); + values[field_num] = value; + DBUG_VOID_RETURN; +} + +/** + Validate sequence values + Require: + 1. max value >= min value + 2. start >= min value + 3. increment >= 1 + 4. max value >= start + + @param[in] item field value + + @retval false valid + @retval true invalid +*/ +bool check_sequence_values_valid(const ulonglong *items) { + DBUG_ENTER("check_sequence_values_valid"); + if (items[Sequence_field::FIELD_NUM_MAXVALUE] >= + items[Sequence_field::FIELD_NUM_MINVALUE] && + items[Sequence_field::FIELD_NUM_START] >= + items[Sequence_field::FIELD_NUM_MINVALUE] && + items[Sequence_field::FIELD_NUM_INCREMENT] >= 1 && + items[Sequence_field::FIELD_NUM_MAXVALUE] > + items[Sequence_field::FIELD_NUM_START]) + DBUG_RETURN(false); + + DBUG_RETURN(true); +} + +/* + Check whether inited values are valid through + syntax: +   CREATE SEQUENCE [IF NOT EXISTS] schema.seqName +    [START WITH ] +    [MINVALUE ] +    [MAXVALUE ] +    [INCREMENT BY ] +    [CACHE | NOCACHE] +    [CYCLE | NOCYCLE] +   ; + + @retval true Invalid + @retval false valid +*/ +bool Sequence_info::check_valid() const { + DBUG_ENTER("Sequence_info::check_valid"); + + if (check_sequence_values_valid(values)) { + my_error(ER_SEQUENCE_INVALID, MYF(0), db, table_name); + DBUG_RETURN(true); + } + DBUG_RETURN(false); +} + +/** + Sequence field getting function + + @param[in] field_num Sequence field number + + @retval ulonglong Sequence field value +*/ +ulonglong Sequence_info::get_value(const Fields field_num) const { + DBUG_ENTER("Sequence_info::get_value"); + DBUG_ASSERT(field_num < FIELD_NUM_END); + DBUG_RETURN(values[field_num]); +} + +/** + Release the ref count if locked +*/ +Sequence_property::~Sequence_property() { + if (m_plugin) plugin_unlock(NULL, m_plugin); +} + +/** + Configure the sequence flags and base db_type when open_table_share. + + @param[in] plugin Storage engine plugin +*/ +void Sequence_property::configure(plugin_ref plugin) { + handlerton *hton; + if (plugin && ((hton = plugin_data(plugin))) && + hton->db_type == DB_TYPE_SEQUENCE_DB) { + if ((m_plugin = ha_resolve_sequence_base(NULL))) { + base_db_type = plugin_data(m_plugin); + m_sequence = true; + } + } +} +/** + Judge the sequence iteration type according to the query string. + + @param[in] table TABLE object + + @retval iteration mode +*/ +Sequence_iter_mode sequence_iteration_type(TABLE *table) { + DBUG_ENTER("sequence_iteration_type"); + if (bitmap_is_set(table->read_set, Sequence_field::FIELD_NUM_NEXTVAL)) + DBUG_RETURN(Sequence_iter_mode::IT_NEXTVAL); + + DBUG_RETURN(Sequence_iter_mode::IT_NON_NEXTVAL); +} + +/** + Check the sequence table fields validation + + @param[in] alter info The alter information + + @retval true Failure + @retval false Success + +*/ +bool check_sequence_fields_valid(Alter_info *alter_info) { + Create_field *field; + List_iterator it(alter_info->create_list); + size_t field_count; + size_t field_no; + DBUG_ENTER("check_sequence_fields_valid"); + + field_count = alter_info->create_list.elements; + field_no = 0; + if (field_count != Sequence_field::FIELD_NUM_END || + alter_info->key_list.size() > 0) + DBUG_RETURN(true); + + while ((field = it++)) { + if (my_strcasecmp(system_charset_info, seq_fields[field_no].field_name, + field->field_name) || + (field->flags != (NOT_NULL_FLAG | NO_DEFAULT_VALUE_FLAG)) || + (field->sql_type != seq_fields[field_no].field_type)) + DBUG_RETURN(true); + + field_no++; + } + DBUG_RETURN(false); +} + +/// @} (end of group Sequence Engine) diff --git a/sql/sequence_common.h b/sql/sequence_common.h new file mode 100644 index 000000000000..577b8dce1a1c --- /dev/null +++ b/sql/sequence_common.h @@ -0,0 +1,278 @@ +/* Copyright (c) 2000, 2017, Alibaba and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef SEQUENCE_COMMON_INCLUDED +#define SEQUENCE_COMMON_INCLUDED + +#include "binary_log_types.h" // MYSQL_TYPE_LONGLONG +#include "map_helpers.h" // collation_unordered_map +#include "sql/psi_memory_key.h" // PSI_memory_key +#include "sql/sql_plugin_ref.h" // plugin_ref + +struct handlerton; +class THD; +struct TABLE; +class Alter_info; + +extern PSI_memory_key key_memory_sequence_last_value; + +/** + Sequence table field value structure. +*/ +struct st_sequence_value { + ulonglong currval; + ulonglong nextval; + ulonglong minvalue; + ulonglong maxvalue; + ulonglong start; + ulonglong increment; + ulonglong cache; + ulonglong cycle; + ulonglong round; +}; + +/** + Sequence create information. +*/ +class Sequence_info { + public: + /* All the sequence fields.*/ + enum Fields { + FIELD_NUM_CURRVAL = 0, + FIELD_NUM_NEXTVAL, + FIELD_NUM_MINVALUE, + FIELD_NUM_MAXVALUE, + FIELD_NUM_START, + FIELD_NUM_INCREMENT, + FIELD_NUM_CACHE, + FIELD_NUM_CYCLE, + FIELD_NUM_ROUND, + /* This must be last! */ + FIELD_NUM_END + }; + + /** + Construtor and Destrutor + */ + Sequence_info(); + virtual ~Sequence_info() {} + + /* Disable the copy and assign function */ + Sequence_info(const Sequence_info &) = delete; + Sequence_info(const Sequence_info &&) = delete; + Sequence_info &operator=(const Sequence_info &) = delete; + + /** + Sequence field setting function + + @param[in] field_num Sequence field number + @param[in] value Sequence field value + + @retval void + */ + void init_value(const Fields field_num, const ulonglong value); + + /** + Sequence field getting function + + @param[in] field_num Sequence field number + + @retval ulonglong Sequence field value + */ + ulonglong get_value(const Fields field_num) const; + + /* + Check whether inited values are valid through + syntax: 'CREATE SEQUENCE ...' + + @retval true Invalid + @retval false valid + */ + bool check_valid() const; + + const char *db; + const char *table_name; + handlerton *base_db_type; /** Sequence table engine */ + private: + /** + Assign initial default values of sequence fields + + @retval void + */ + void init_default(); + + ulonglong values[Fields::FIELD_NUM_END]; +}; + +typedef Sequence_info::Fields Sequence_field; + +/** + Sequence table fields definition. +*/ +struct st_sequence_field_info { + const char *field_name; + const char *field_length; + const Sequence_field field_num; + const enum enum_field_types field_type; + const LEX_STRING comment; +}; + +/** + The sequence value structure should be consistent with Sequence field + definition +*/ +static_assert(sizeof(ulonglong) * Sequence_field::FIELD_NUM_END == + sizeof(struct st_sequence_value), + ""); + +/** + Sequence attributes within TABLE_SHARE object, label the table as sequence + table. +*/ +class Sequence_property { + public: + Sequence_property() : m_sequence(false), base_db_type(NULL), m_plugin(NULL) {} + + ~Sequence_property(); + + /* Disable these copy and assign functions */ + Sequence_property(const Sequence_property &) = delete; + Sequence_property(const Sequence_property &&) = delete; + Sequence_property &operator=(const Sequence_property &) = delete; + + /** + Configure the sequence flags and base db_type when open_table_share. + + @param[in] plugin Storage engine plugin + */ + void configure(plugin_ref plugin); + bool is_sequence() { return m_sequence; } + handlerton *db_type() { return base_db_type; } + + private: + bool m_sequence; + handlerton *base_db_type; + plugin_ref m_plugin; +}; + +/** + Sequence scan mode in TABLE object. +*/ +class Sequence_scan { + public: + /** + Scan mode example like: + + ORIGINAL_SCAN 'SELECT * FROM s' + ITERATION_SCAN 'SELECT NEXTVAL(s), CURRVAL(s)' + + Orignal scan only query the base table data. + Iteration scan will apply the sequence logic. + */ + enum Scan_mode { ORIGINAL_SCAN = 0, ITERATION_SCAN }; + + enum Iter_mode { + IT_NON, /* Query the sequence base table */ + IT_NEXTVAL, /* Query nextval */ + IT_NON_NEXTVAL, /* Query non nextval, maybe currval or others */ + }; + + Sequence_scan() : m_mode(ORIGINAL_SCAN) {} + + void reset() { m_mode = ORIGINAL_SCAN; } + void set(Scan_mode mode) { m_mode = mode; } + Scan_mode get() { return m_mode; } + + /* Overlap the assignment operator */ + Sequence_scan &operator=(const Sequence_scan &rhs) { + if (this != &rhs) { + this->m_mode = rhs.m_mode; + } + return *this; + } + + private: + Scan_mode m_mode; +}; + +typedef Sequence_scan::Scan_mode Sequence_scan_mode; +typedef Sequence_scan::Iter_mode Sequence_iter_mode; + +/** + Sequence currval that was saved in THD object. +*/ +class Sequence_last_value { + public: + Sequence_last_value(){}; + virtual ~Sequence_last_value(){}; + + void set_version(ulonglong version) { m_version = version; } + ulonglong get_version() { return m_version; } + + ulonglong m_values[Sequence_field::FIELD_NUM_END]; + + private: + ulonglong m_version; +}; + +typedef collation_unordered_map + Sequence_last_value_hash; + +extern const LEX_STRING SEQUENCE_ENGINE_NAME; +extern const LEX_STRING SEQUENCE_BASE_ENGINE_NAME; + +/** + Resolve the sequence engine and sequence base engine, it needs to + unlock_plugin explicitly if thd is null; +*/ +extern plugin_ref ha_resolve_sequence(const THD *thd); +extern plugin_ref ha_resolve_sequence_base(const THD *thd); + +extern st_sequence_field_info seq_fields[]; + +extern bool check_sequence_values_valid(const ulonglong *items); +extern bool check_sequence_fields_valid(Alter_info *alter_info); + +extern Sequence_iter_mode sequence_iteration_type(TABLE *table); + +/** + Clear or destroy the global Sequence_share_hash and + session Sequence_last_value_hash. +*/ +template +void clear_hash(collation_unordered_map *hash) { + if (hash) { + for (auto it = hash->cbegin(); it != hash->cend(); ++it) { + delete it->second; + } + hash->clear(); + } +} +template +void destroy_hash(collation_unordered_map *hash) { + if (hash) { + clear_hash(hash); + delete hash; + } +} + +#endif diff --git a/sql/sequence_transaction.cc b/sql/sequence_transaction.cc new file mode 100644 index 000000000000..b6f8df84ad87 --- /dev/null +++ b/sql/sequence_transaction.cc @@ -0,0 +1,148 @@ +/* Copyright (c) 2000, 2018, Alibaba and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** + @file + + Implementation of Sequence autonomous transaction. +*/ + +#include "sql/sequence_transaction.h" +#include "sql/sequence_common.h" +#include "sql/sql_base.h" +#include "sql/sql_class.h" +#include "sql/transaction.h" + + +/** + @addtogroup Sequence Engine + + Sequence autonomous transaction + + @{ +*/ + +/** + Constructor of autonomous transaction. +*/ +THD::Autonomous_trx_rw::Autonomous_trx_rw(THD *thd, Attachable_trx *prev_trx) + : Attachable_trx(thd, prev_trx) { + init_autonomous(); +} + +/** + Cleanup the autonomous transaction. + */ +THD::Autonomous_trx_rw::~Autonomous_trx_rw() {} + +/** + The autonomous is readwrite, and will generate binlog, so inherit the + option_bits. +*/ +void THD::Autonomous_trx_rw::init_autonomous() { + m_thd->tx_read_only = false; + m_thd->variables.option_bits = m_trx_state.m_thd_option_bits; + m_is_autonomous = true; +} + +void THD::begin_autonomous_rw_transaction() { + m_attachable_trx = new Autonomous_trx_rw(this, m_attachable_trx); +} + +void THD::end_autonomous_rw_transaction() { + Attachable_trx *prev_trx = m_attachable_trx->get_prev_attachable_trx(); + delete m_attachable_trx; + m_attachable_trx = prev_trx; +} + +bool THD::is_autonomous_transaction() const { + return is_attachable_rw_transaction_active() && + m_attachable_trx->is_autonomous(); +} + +/** + Update the base table and reflush the cache. + + @param[in] super_table The query opened table. Here will open + other one to do updating. + + @retval 0 Success + @retval ~0 Failure +*/ +int Reload_sequence_cache_ctx::reload_sequence_cache(TABLE *super_table) { + int error = 0; + TABLE *table; + DBUG_ENTER("Reload_sequence_cache_ctx::reload_sequence_cache"); + + /** + Report error and return the uniform error HA_ERR_SEQUENCE_ACCESS_FAILURE. + since this function is called by ha_sequence. + */ + if (!(m_thd->is_current_stmt_binlog_disabled()) && + !(m_thd->is_current_stmt_binlog_format_row())) { + my_error(ER_SEQUENCE_BINLOG_FORMAT, MYF(0)); + goto err; + } + + /* Open the sequence table */ + if ((error = m_trans.get_otx()->open_table())) goto err; + + table = m_trans.get_otx()->get_table(); + + if ((error = table->file->ha_flush_cache(super_table))) { + trans_rollback_stmt(m_thd); + trans_rollback(m_thd); + /* Here the error is handler error, so return it directly. */ + DBUG_RETURN(error); + } else { + if (trans_commit_stmt(m_thd) || trans_commit(m_thd)) { + trans_rollback(m_thd); + goto err; + } + } + DBUG_RETURN(0); + +err: + DBUG_RETURN(HA_ERR_SEQUENCE_ACCESS_FAILURE); +} + +/** + Constructor of Sequence transaction. + Open a new autonomous transaction by backup current transaction context +*/ +Sequence_transaction::Sequence_transaction(THD *thd, TABLE_SHARE *share) + : m_otx(thd, share), m_thd(thd) { + m_thd->begin_autonomous_rw_transaction(); +} +/** + Destructor of Sequence transaction. + End the autonomous transaction by restore transaction context +*/ +Sequence_transaction::~Sequence_transaction() { + m_thd->end_autonomous_rw_transaction(); +} + +Reload_sequence_cache_ctx::~Reload_sequence_cache_ctx() { + m_thd->in_sub_stmt = m_saved_in_sub_stmt; +} + +/// @} (end of group Sequence Engine) diff --git a/sql/sequence_transaction.h b/sql/sequence_transaction.h new file mode 100644 index 000000000000..2f55ee87de3e --- /dev/null +++ b/sql/sequence_transaction.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2000, 2018, Alibaba and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef SEQUENCE_TRANSACTION_INCLUDED +#define SEQUENCE_TRANSACTION_INCLUDED + +#include "sql/sql_sequence.h" // Open_sequence_table_ctx + +class THD; +struct TABLE_SHARE; + +/** + Sequence updating base table transaction is autonomous: + Firstly backup current transaction context. + Secondly commit inner transaction directly. + At last restore the backed transaction context. +*/ +class Sequence_transaction { + public: + explicit Sequence_transaction(THD *thd, TABLE_SHARE *share); + + virtual ~Sequence_transaction(); + + /** + Get opened table context. + + @retval m_otx Opened table context + */ + Open_sequence_table_ctx *get_otx() { return &m_otx; } + + private: + Open_sequence_table_ctx m_otx; + THD *m_thd; +}; + +/** + Updating the base sequence table context. + It will update the base table, and reflush the sequence share cache. +*/ +class Reload_sequence_cache_ctx { + public: + explicit Reload_sequence_cache_ctx(THD *thd, TABLE_SHARE *share) + : m_trans(thd, share), m_thd(thd), m_saved_in_sub_stmt(thd->in_sub_stmt) { + thd->in_sub_stmt = false; + } + + virtual ~Reload_sequence_cache_ctx(); + /** + Update the base table and reflush the cache. + + @param[in] super_table The query opened table.Here will open + other one to do updating. + + @retval 0 Success + @retval ~0 Failure + */ + int reload_sequence_cache(TABLE *super_table); + + private: + Sequence_transaction m_trans; + THD *m_thd; + bool m_saved_in_sub_stmt; +}; + +#endif diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 90d094bca108..dbfecf3a4960 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3436,6 +3436,19 @@ retry_share : { table->init(thd, table_list); + /** + 1. NEXTVAL() or CURRVAL() function only worked on sequence table. + 2. Iteration scan on sequence will release MDL lock when statement end. + */ + if (table->sequence_scan.get() == Sequence_scan_mode::ITERATION_SCAN) { + if (!table->s->sequence_property->is_sequence()) { + my_error(ER_TABLE_IS_NOT_SEQUENCE, MYF(0), table_list->db, + table_list->table_name); + DBUG_RETURN(true); + } + table->mdl_ticket->set_autonomous(true); + } + /* Request a read lock for implicitly opened P_S tables. */ if (in_LTM(thd) && table_list->table->file->get_lock_type() == F_UNLCK && belongs_to_p_s(table_list)) { @@ -6374,7 +6387,8 @@ bool open_and_lock_tables(THD *thd, TABLE_LIST *tables, uint flags, */ DBUG_ASSERT(!thd->is_attachable_ro_transaction_active() && (!thd->is_attachable_rw_transaction_active() || - !strcmp(tables->table_name, "gtid_executed"))); + !strcmp(tables->table_name, "gtid_executed") || + thd->is_autonomous_transaction())); if (open_tables(thd, &tables, &counter, flags, prelocking_strategy)) goto err; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 9ef4791392f2..889acb1aa36b 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -88,6 +88,8 @@ #include "sql/xa.h" #include "thr_mutex.h" +#include "sql/sequence_common.h" // Sequence_last_value_hash + using std::max; using std::min; using std::unique_ptr; @@ -240,6 +242,8 @@ void THD::Attachable_trx::init() { influencing attachable transaction we are initiating. */ m_thd->transaction_rollback_request = false; + + m_is_autonomous = false; } THD::Attachable_trx::~Attachable_trx() { @@ -258,7 +262,8 @@ THD::Attachable_trx::~Attachable_trx() { // (for example, when statement is killed just after tables are locked but // before any other operations on the table happes). We try not to rely on // it in other places on SQL-layer as well. - trans_commit_attachable(m_thd); + + if (!m_is_autonomous) trans_commit_attachable(m_thd); // Close all the tables that are open till now. @@ -547,11 +552,14 @@ THD::THD(bool enable_plugins) #ifndef DBUG_OFF debug_binlog_xid_last.reset(); #endif + + seq_thd_hash = new Sequence_last_value_hash(system_charset_info, + key_memory_sequence_last_value); } void THD::set_transaction(Transaction_ctx *transaction_ctx) { - DBUG_ASSERT(is_attachable_ro_transaction_active()); - + DBUG_ASSERT(is_attachable_ro_transaction_active() || + is_autonomous_transaction()); delete m_transaction.release(); m_transaction.reset(transaction_ctx); } @@ -875,6 +883,8 @@ void THD::cleanup_connection(void) { } /* DEBUG code only (end) */ #endif + + clear_hash(seq_thd_hash); } /* @@ -967,6 +977,7 @@ void THD::cleanup(void) { */ session_tracker.deinit(); + clear_hash(seq_thd_hash); /* If we have a Security_context, make sure it is "logged out" */ @@ -1092,6 +1103,9 @@ THD::~THD() { if (m_token_array != NULL) { my_free(m_token_array); } + + destroy_hash(seq_thd_hash); + seq_thd_hash = NULL; DBUG_VOID_RETURN; } diff --git a/sql/sql_class.h b/sql/sql_class.h index 4bbe2d9ad8fa..7c74dfb47026 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -153,6 +153,10 @@ struct LOG_INFO; typedef struct user_conn USER_CONN; struct MYSQL_LOCK; +class Sequence_last_value; +typedef collation_unordered_map + Sequence_last_value_hash; + extern "C" void thd_enter_cond(void *opaque_thd, mysql_cond_t *cond, mysql_mutex_t *mutex, const PSI_stage_info *stage, @@ -1607,6 +1611,7 @@ class THD : public MDL_context_owner, return m_prev_attachable_trx; }; virtual bool is_read_only() const { return true; } + bool is_autonomous() const { return m_is_autonomous; } void init(); @@ -1616,6 +1621,7 @@ class THD : public MDL_context_owner, enum_reset_lex m_reset_lex; + bool m_is_autonomous; /** Attachable_trx which was active for the THD before when this transaction was started (NULL in most cases). @@ -1655,6 +1661,19 @@ class THD : public MDL_context_owner, Attachable_trx_rw &operator=(const Attachable_trx_rw &); }; + class Autonomous_trx_rw : public Attachable_trx { + public: + virtual bool is_read_only() const { return false; } + Autonomous_trx_rw(THD *thd, Attachable_trx *prev_trx); + ~Autonomous_trx_rw(); + + void init_autonomous(); + + private: + Autonomous_trx_rw(const Autonomous_trx_rw &); + Autonomous_trx_rw &operator=(const Autonomous_trx_rw &); + }; + Attachable_trx *m_attachable_trx; public: @@ -2956,6 +2975,10 @@ class THD : public MDL_context_owner, */ bool is_attachable_rw_transaction_active() const; + void begin_autonomous_rw_transaction(); + void end_autonomous_rw_transaction(); + bool is_autonomous_transaction() const; + public: /* @todo Make these methods private or remove them completely. Only @@ -3968,6 +3991,12 @@ class THD : public MDL_context_owner, bool is_waiting_for_disk_space() const { return waiting_for_disk_space; } bool sql_parser(); + + private: + Sequence_last_value_hash *seq_thd_hash; + + public: + Sequence_last_value_hash *get_sequence_hash() { return seq_thd_hash; } }; inline void THD::vsyntax_error_at(const YYLTYPE &location, const char *format, diff --git a/sql/sql_cmd_ddl_table.h b/sql/sql_cmd_ddl_table.h index 99fd75868917..5c3bb208593f 100644 --- a/sql/sql_cmd_ddl_table.h +++ b/sql/sql_cmd_ddl_table.h @@ -58,7 +58,7 @@ class Sql_cmd_ddl_table : public Sql_cmd { inline Sql_cmd_ddl_table::~Sql_cmd_ddl_table() {} -class Sql_cmd_create_table final : public Sql_cmd_ddl_table { +class Sql_cmd_create_table : public Sql_cmd_ddl_table { public: Sql_cmd_create_table(Alter_info *alter_info, TABLE_LIST *query_expression_tables) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 0e73aa59ae38..c07dbda3968a 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -471,6 +471,8 @@ void LEX::reset() { option_type = OPT_DEFAULT; clear_privileges(); + + sequence_info = NULL; } /** diff --git a/sql/sql_lex.h b/sql/sql_lex.h index d88d32fb84c3..dd394eb86a1c 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -100,6 +100,8 @@ #include "thr_lock.h" // thr_lock_type #include "violite.h" // SSL_type +#include "sql/sequence_common.h" // Sequence_scan + class Item_func_set_user_var; class Item_sum; class Parse_tree_root; @@ -131,6 +133,7 @@ class THD; class Window; struct MEM_ROOT; struct Sql_cmd_srs_attributes; +class Sequence_info; enum class enum_jt_column; enum class enum_jtc_on : uint16; @@ -313,6 +316,8 @@ enum enum_drop_mode { #define TL_OPTION_IGNORE_LEAVES 4 #define TL_OPTION_ALIAS 8 +#define TL_OPTION_SEQUENCE 16 + /* Structure for db & table in sql_yacc */ extern LEX_CSTRING EMPTY_CSTR; extern LEX_CSTRING NULL_CSTR; @@ -1360,7 +1365,8 @@ class SELECT_LEX { THD *thd, Table_ident *table, const char *alias, ulong table_options, thr_lock_type flags = TL_UNLOCK, enum_mdl_type mdl_type = MDL_SHARED_READ, List *hints = 0, List *partition_names = 0, - LEX_STRING *option = 0, Parse_context *pc = NULL); + LEX_STRING *option = 0, Parse_context *pc = NULL, + Sequence_scan_mode seq_scan_mode = Sequence_scan_mode::ORIGINAL_SCAN); TABLE_LIST *get_table_list() const { return table_list.first; } bool init_nested_join(THD *thd); TABLE_LIST *end_nested_join(); @@ -2250,6 +2256,8 @@ union YYSTYPE { class PT_column_def *column_def; class PT_table_element *table_element; Mem_root_array *table_element_list; + class PT_create_table_option *opt_sequence_option; + Mem_root_array *opt_sequence_options; struct { Mem_root_array *opt_create_table_options; PT_partition *opt_partitioning; @@ -4057,6 +4065,8 @@ struct LEX : public Query_tables_list { void clear_privileges(); bool make_sql_cmd(Parse_tree_root *parse_tree); + + Sequence_info *sequence_info; }; /** diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index fce96eb8543b..b04fea983e46 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -5553,10 +5553,13 @@ TABLE_LIST *SELECT_LEX::add_table_to_list( THD *thd, Table_ident *table_name, const char *alias, ulong table_options, thr_lock_type lock_type, enum_mdl_type mdl_type, List *index_hints_arg, List *partition_names, - LEX_STRING *option, Parse_context *pc) { + LEX_STRING *option, Parse_context *pc, Sequence_scan_mode seq_scan_mode) { TABLE_LIST *previous_table_ref = NULL; /* The table preceding the current one. */ LEX *lex = thd->lex; + + bool sequence_query; + DBUG_ENTER("add_table_to_list"); DBUG_ASSERT(table_name != nullptr); @@ -5620,6 +5623,8 @@ TABLE_LIST *SELECT_LEX::add_table_to_list( DBUG_RETURN(0); } + sequence_query = (table_options & TL_OPTION_SEQUENCE); + ptr->set_tableno(0); ptr->set_lock({lock_type, THR_DEFAULT}); ptr->updating = (table_options & TL_OPTION_UPDATING); @@ -5707,7 +5712,7 @@ TABLE_LIST *SELECT_LEX::add_table_to_list( } } /* Store the table reference preceding the current one. */ - if (table_list.elements > 0) { + if (table_list.elements > 0 && !sequence_query) { /* table_list.next points to the last inserted TABLE_LIST->next_local' element @@ -5732,7 +5737,8 @@ TABLE_LIST *SELECT_LEX::add_table_to_list( previous table reference to 'ptr'. Here we also add one element to the list 'table_list'. */ - table_list.link_in_list(ptr, &ptr->next_local); + if (!sequence_query) table_list.link_in_list(ptr, &ptr->next_local); + ptr->next_name_resolution_table = NULL; ptr->partition_names = partition_names; /* Link table in global list (all used tables) */ @@ -5779,6 +5785,8 @@ TABLE_LIST *SELECT_LEX::add_table_to_list( } } + ptr->sequence_scan.set(seq_scan_mode); + DBUG_RETURN(ptr); } diff --git a/sql/sql_sequence.cc b/sql/sql_sequence.cc new file mode 100644 index 000000000000..ed3370a749cc --- /dev/null +++ b/sql/sql_sequence.cc @@ -0,0 +1,426 @@ +/* Copyright (c) 2015, 2018, Alibaba and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** + @file + + Implementation of SEQUENCE object + + CREATE SEQUENCE syntax: + +   CREATE SEQUENCE [IF NOT EXISTS] schema.seqName +    [START WITH ] +    [MINVALUE ] +    [MAXVALUE ] +    [INCREMENT BY ] +    [CACHE | NOCACHE] +    [CYCLE | NOCYCLE] +   ; + Or: + CREATE TABLE `s` ( + `currval` bigint(21) NOT NULL COMMENT 'current value', + `nextval` bigint(21) NOT NULL COMMENT 'next value', + `minvalue` bigint(21) NOT NULL COMMENT 'min value', + `maxvalue` bigint(21) NOT NULL COMMENT 'max value', + `start` bigint(21) NOT NULL COMMENT 'start value', + `increment` bigint(21) NOT NULL COMMENT 'increment value', + `cache` bigint(21) NOT NULL COMMENT 'cache size', + `cycle` bigint(21) NOT NULL COMMENT 'cycle state', + `round` bigint(21) NOT NULL COMMENT 'already how many round' + ) ENGINE=Sequence DEFAULT CHARSET=latin1 + insert into s values(0,0,1,10,1,1,2,0,0); + commit; + + SHOW syntax: + +   SHOW CREATE TABLE schema.seqName; + + QUERY syntax:    + + 1.    +   SELECT [nextval | currval | *] FROM schema.seqName; + + 2. TODO:  +   SELECT seqName.nextval from dual; +   SELECT seqName.currval from dual; + + 3. + SELECT NEXTVAL(seq); + SELECT CURRVAL(seq); + + Usage: +   use test; create sequence s; +   create table t(id int); +   insert into t values (nextval(s)) +*/ + +/** + @addtogroup Sequence Engine + + Sequence Engine syntax statement implementation. + + @{ +*/ +#include +#include "sql/dd/types/abstract_table.h" +#include "sql/derror.h" // ER_THD +#include "sql/field.h" +#include "sql/parse_tree_column_attrs.h" +#include "sql/sql_base.h" +#include "sql/sql_class.h" +#include "sql/sql_table.h" +#include "sql/transaction.h" + +#include "sql/sequence_common.h" +#include "sql/sql_sequence.h" +/** + Prepare sequence base engine. + + @param[in] thd Connection context + @param[in] table TABLE_LIST object + + @retval false success + @retval true failure +*/ +bool PT_create_sequence_stmt::prepare_sequence_engine(const THD *thd, + const TABLE_LIST *table) { + DBUG_ENTER("PT_create_sequence_stmt::prepare_sequence_engine"); + + /* Step 1: prepare the db and table_name */ + m_sequence_info.db = table->db; + m_sequence_info.table_name = table->table_name; + + /* Step 2: prepare the base engine */ + plugin_ref sequence_plugin = ha_resolve_sequence(thd); + plugin_ref base_plugin = ha_resolve_sequence_base(thd); + + DBUG_EXECUTE_IF("sequence_engine_error", { sequence_plugin = NULL; }); + + if (sequence_plugin == nullptr || + plugin_data(sequence_plugin) == nullptr) { + my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), SEQUENCE_ENGINE_NAME.str); + DBUG_RETURN(true); + } + /* We will change the create_info.db_type when create_table_impl() */ + if (!((m_create_info.db_type = m_sequence_info.base_db_type = + (base_plugin != nullptr ? plugin_data(base_plugin) + : nullptr)))) { + my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), SEQUENCE_BASE_ENGINE_NAME.str); + DBUG_RETURN(true); + } + + m_create_info.used_fields |= HA_CREATE_USED_ENGINE; + + DBUG_RETURN(false); +} + +/** + Prepare and check sequence table columns + + @param[in] thd Connection context + + @retval false success + @retval true failure +*/ +bool PT_create_sequence_stmt::prepare_sequence_fields(const THD *thd) { + const st_sequence_field_info *field_def; + MEM_ROOT *mem_root; + LEX_STRING field_name; + size_t field_name_len; + PT_column_attr_base *not_null_attr; + PT_column_attr_base *comment_attr; + Mem_root_array *column_attrs; + + PT_type *field_type; + PT_field_def_base *field_def_base; + PT_table_element *table_element; + Mem_root_array *table_element_list; + DBUG_ENTER("PT_create_sequence_stmt::prepare_sequence_fields"); + DBUG_ASSERT(opt_table_element_list == NULL); + /** + Columns definition structure: + + 1.-- PT_table_element list + 2.-- PT_column_def + 3.-- Field_name (LEX_STRING) + 3.-- PT_field_def + 4.-- PT_numeric_type + 4.-- PT_column_attr_base list + 5.-- PT_not_null_column_attr + 5.-- PT_comment_column_attr + */ + field_def = seq_fields; + mem_root = thd->mem_root; + + /* Native sequence create syntax */ + table_element_list = + new (mem_root) Mem_root_array(mem_root); + while (field_def->field_name) { + /* Column attrs include not_null and comment */ + column_attrs = + new (mem_root) Mem_root_array(mem_root); + comment_attr = new (mem_root) PT_comment_column_attr(field_def->comment); + not_null_attr = new (mem_root) PT_not_null_column_attr; + + column_attrs->push_back(not_null_attr); + column_attrs->push_back(comment_attr); + + /* Column type is bigint(21) */ + field_type = new (mem_root) PT_numeric_type( + Int_type::BIGINT, field_def->field_length, Field_option::NONE); + field_def_base = new (mem_root) PT_field_def(field_type, column_attrs); + + field_name_len = strlen(field_def->field_name); + field_name.str = thd->strmake(field_def->field_name, field_name_len); + field_name.length = field_name_len; + + /* Column def and column name constitute column element */ + table_element = + new (mem_root) PT_column_def(field_name, field_def_base, NULL); + table_element_list->push_back(table_element); + field_def++; + } + /* Assign the PT_create_table_stmt attribute */ + opt_table_element_list = table_element_list; + DBUG_RETURN(false); +} +/** + Check the fields whether they are consistent with pre-defined. + + @param[in] alter_info All the DDL information + + @retval false success + @retval true failure +*/ +bool PT_create_sequence_stmt::check_sequence_fields( + Alter_info *alter_info) const { + bool error = false; + DBUG_ENTER("PT_create_sequence_stmt::check_sequence_fields"); + + if ((error = check_sequence_fields_valid(alter_info))) + my_error(ER_SEQUENCE_INVALID, MYF(0), m_sequence_info.db, + m_sequence_info.table_name); + + DBUG_RETURN(error); +} +/** + CREATE SEQUENCE statement command + + @param[in] thd Connection context + + @retval Sql_cmd SQL command +*/ +Sql_cmd *PT_create_sequence_stmt::make_cmd(THD *thd) { + LEX *const lex = thd->lex; + + lex->sql_command = SQLCOM_CREATE_TABLE; + + Parse_context pc(thd, lex->current_select()); + + TABLE_LIST *table = pc.select->add_table_to_list( + thd, table_name, NULL, TL_OPTION_UPDATING, TL_WRITE, MDL_SHARED); + if (table == NULL) return NULL; + + table->open_strategy = TABLE_LIST::OPEN_FOR_CREATE; + + /* Step 1: prepare sequence engine. */ + if (prepare_sequence_engine(thd, table)) return NULL; + + /* Step 2: prepare sequence table columns */ + if (prepare_sequence_fields(thd)) return NULL; + + lex->create_info = &m_create_info; + lex->sequence_info = &m_sequence_info; + lex->create_info->sequence_info = &m_sequence_info; + + Table_ddl_parse_context pc2(thd, pc.select, &m_alter_info); + + pc2.create_info->options = 0; + if (only_if_not_exists) + pc2.create_info->options |= HA_LEX_CREATE_IF_NOT_EXISTS; + + pc2.create_info->default_table_charset = NULL; + + lex->name.str = 0; + lex->name.length = 0; + + /* Step 3: Contextualize sequence row values */ + if (opt_create_sequence_options) { + for (auto option : *opt_create_sequence_options) + if (option->contextualize(&pc2)) return NULL; + } + /* Step 4: check sequence row values */ + if (pc2.sequence_info->check_valid()) return NULL; + + /* Step 5: Contextualize sequence columns */ + TABLE_LIST *qe_tables = nullptr; + if (opt_table_element_list) { + for (auto element : *opt_table_element_list) { + if (element->contextualize(&pc2)) return NULL; + } + } + + if (check_sequence_fields(&m_alter_info)) return NULL; + + switch (on_duplicate) { + case On_duplicate::IGNORE_DUP: + lex->set_ignore(true); + break; + case On_duplicate::REPLACE_DUP: + lex->duplicates = DUP_REPLACE; + break; + case On_duplicate::ERROR: + lex->duplicates = DUP_ERROR; + break; + } + + DBUG_ASSERT(opt_query_expression == NULL); + lex->set_current_select(pc.select); + + DBUG_ASSERT((pc2.create_info->used_fields & HA_CREATE_USED_ENGINE) && + pc2.create_info->db_type); + + create_table_set_open_action_and_adjust_tables(lex); + + thd->lex->alter_info = &m_alter_info; + return new (thd->mem_root) + Sql_cmd_create_sequence(&m_alter_info, qe_tables, &m_sequence_info); +} + +/** + Create the sequence table and insert a row into table. + + @param[in] thd User connection context + + @retval false Success + @retval true Failure +*/ +bool Sql_cmd_create_sequence::execute(THD *thd) { + LEX *const lex = thd->lex; + DBUG_ENTER("Sql_cmd_create_sequence::execute"); + DBUG_ASSERT(lex->sequence_info); + + DBUG_RETURN(super::execute(thd)); +} + +bool Sql_cmd_create_sequence::prepare(THD *thd) { return super::prepare(thd); } + +/** + Construtor of Open_sequence_table_ctx +*/ +Open_sequence_table_ctx::Open_sequence_table_ctx(THD *thd, + TABLE_LIST *table_list) + : m_thd(thd), m_inherit_table(table_list), m_state(thd, table_list) {} + +Open_sequence_table_ctx::Open_sequence_table_ctx(THD *thd, TABLE_SHARE *share) + : m_thd(thd), m_inherit_table(NULL), m_state(thd, share) {} +/** + Open and lock the sequence table. + + @retval false success + @retval true failure +*/ +bool Open_sequence_table_ctx::open_table() { + DBUG_ENTER("Open_sequence_table_ctx::open_table"); + + if (!m_inherit_table || !(m_inherit_table->table)) { + /** + Clone a TABLE_LIST from table, then open and lock the table for the + sequence dml. + */ + if (open_and_lock_tables(m_thd, m_state.cloned_table(), + MYSQL_LOCK_IGNORE_TIMEOUT)) + DBUG_RETURN(true); + } + + DBUG_ASSERT((m_thd->mdl_context.owns_equal_or_stronger_lock( + MDL_key::TABLE, m_state.cloned_table()->db, + m_state.cloned_table()->table_name, MDL_SHARED_WRITE))); + + /** + Insert the initial row soon after CREATE SEQUENCE, + we will inherit transaction context. + */ + DBUG_RETURN(false); +} + +/** + We will inherit the CREATE SEQUENCE transaction context, + so didn't need to close opened_table and release MDL explicitly. + + Opened table and MDL wil be released when statement end that will incur + implicit commit. +*/ +Open_sequence_table_ctx::~Open_sequence_table_ctx() {} + +/** + Construtor of Insert_sequence_table_ctx, for saving current context. +*/ +Insert_sequence_table_ctx::Insert_sequence_table_ctx( + THD *thd, TABLE_LIST *table_list, const Sequence_info *seq_info) + : otx(thd, table_list), + m_thd(thd), + m_seq_info(seq_info), + m_save_binlog_row_based(false) { + /* Sequence will be replicated by statement, so disable row binlog */ + if ((m_save_binlog_row_based = m_thd->is_current_stmt_binlog_format_row())) + m_thd->clear_current_stmt_binlog_format_row(); +} + +Insert_sequence_table_ctx::~Insert_sequence_table_ctx() { + if (m_save_binlog_row_based) m_thd->set_current_stmt_binlog_format_row(); +} +/** + Write the sequence initial row. + + @retval false success + @retval true failure +*/ +bool Insert_sequence_table_ctx::write_record() { + bool error = false; + TABLE *table; + st_sequence_field_info *field_info; + Sequence_field field_num; + ulonglong field_value; + DBUG_ENTER("Insert_sequence_table_ctx::write_record"); + + if ((error = otx.open_table())) DBUG_RETURN(error); + + table = otx.get_table(); + + DBUG_ASSERT(table && table->in_use == m_thd); + table->use_all_columns(); + + field_info = seq_fields; + while (field_info->field_name) { + field_num = field_info->field_num; + field_value = m_seq_info->get_value(field_num); + table->field[field_num]->store((longlong)(field_value), true); + field_info++; + } + if ((error = table->file->ha_write_row(table->record[0]))) { + table->file->print_error(error, MYF(0)); + } + DBUG_RETURN(error); +} + +/// @} (end of group Sequence Engine) diff --git a/sql/sql_sequence.h b/sql/sql_sequence.h new file mode 100644 index 000000000000..32f2de403a38 --- /dev/null +++ b/sql/sql_sequence.h @@ -0,0 +1,245 @@ +/* Copyright (c) 2000, 2018, Alibaba and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef SQL_SEQUENCE_INCLUDED +#define SQL_SEQUENCE_INCLUDED + +#include "handler.h" +#include "lex_string.h" // LEX_STRING +#include "my_inttypes.h" +#include "sql/mem_root_array.h" // MEM_ROOT +#include "sql/parse_tree_nodes.h" +#include "sql/sql_cmd_ddl_table.h" // Sql_cmd_create_table + +#include "sql/sequence_common.h" // Sequence_info + +/** + Sequence engine as the builtin plugin. + The base table engine now only support InnoDB plugin. +*/ + +/** + The struture of SEQUENCE options parser +*/ +template +class PT_values_create_sequence_option : public PT_create_table_option { + public: + explicit PT_values_create_sequence_option(FIELD_TYPE value) : value(value) {} + + virtual ~PT_values_create_sequence_option() {} + + bool contextualize(Table_ddl_parse_context *pc) override { + if (PT_create_table_option::contextualize(pc)) return true; + pc->sequence_info->init_value(field_num, value); + return false; + } + + private: + FIELD_TYPE value; +}; + +class PT_create_sequence_stmt : public PT_create_table_stmt { + public: + /** + @param mem_root MEM_ROOT to use for allocation + @param only_if_not_exists True if @SQL{CREATE %TABLE ... @B{IF NOT EXISTS}} + @param table_name @SQL{CREATE %TABLE ... @B{@}} + @param on_duplicate DUPLICATE, IGNORE or fail with an error + on data duplication errors (relevant + for @SQL{CREATE TABLE ... SELECT} + statements). + @param opt_create_sequence_options + For @SQL {CREATE SEQUENCE ...} + */ + explicit PT_create_sequence_stmt( + MEM_ROOT *mem_root, bool only_if_not_exists, Table_ident *table_name, + On_duplicate on_duplicate, const Mem_root_array + *opt_create_sequence_options) + : PT_create_table_stmt(mem_root, false, only_if_not_exists, table_name, + NULL, NULL, NULL, on_duplicate, NULL), + opt_create_sequence_options(opt_create_sequence_options) {} + + /** + CREATE SEQUENCE statement command + + @param[in] thd Connection context + + @retval Sql_cmd SQL command + */ + Sql_cmd *make_cmd(THD *thd) override; + /** + Prepare sequence base engine + + @param[in] thd Connection context + @param[in] table TABLE_LIST + + @retval false success + @retval true failure + */ + bool prepare_sequence_engine(const THD *thd, const TABLE_LIST *table); + /** + Prepare and check sequence table columns + + @param[in] thd Connection context + + @retval false success + @retval true failure + */ + bool prepare_sequence_fields(const THD *thd); + /** + Check the fields whether it is consistent with pre-defined. + + @param[in] alter_info All the DDL information + + @retval false success + @retval true failure + */ + bool check_sequence_fields(Alter_info *alter_info) const; + + private: + const Mem_root_array *opt_create_sequence_options; + Sequence_info m_sequence_info; +}; + +/** + CREATE SEQUENCE statement cmd +*/ +class Sql_cmd_create_sequence : public Sql_cmd_create_table { + typedef Sql_cmd_create_table super; + + public: + explicit Sql_cmd_create_sequence(Alter_info *alter_info, + TABLE_LIST *query_expression_tables, + Sequence_info *sequence_info) + : Sql_cmd_create_table(alter_info, query_expression_tables), + m_sequence_info(sequence_info) {} + + /* CREATE SEQUENCE is also SQLCOM_CREATE_TABLE */ + enum_sql_command sql_command_code() const override { + return SQLCOM_CREATE_TABLE; + } + bool execute(THD *thd) override; + bool prepare(THD *thd) override; + + private: + const Sequence_info *m_sequence_info; +}; + +class Open_sequence_table_ctx { + public: + /** + Temporary TABLE_LIST object that is used to hold opened table. + */ + class Table_list_state { + public: + /* Used by CREATE SEQUENCE. */ + Table_list_state(THD *thd, TABLE_LIST *table) { + m_table = new (thd->mem_root) TABLE_LIST(); + m_table->init_one_table(table->db, table->db_length, table->table_name, + table->table_name_length, table->alias, + TL_WRITE_CONCURRENT_INSERT, MDL_SHARED_WRITE); + m_table->open_strategy = TABLE_LIST::OPEN_IF_EXISTS; + m_table->open_type = OT_BASE_ONLY; + } + + /* Used by reload sequence share cache */ + Table_list_state(THD *thd, TABLE_SHARE *share) { + m_table = new (thd->mem_root) TABLE_LIST(); + char *db = (char *)thd->memdup(share->db.str, share->db.length + 1); + char *table_name = (char *)thd->memdup(share->table_name.str, + share->table_name.length + 1); + char *alias = (char *)thd->memdup(share->table_name.str, + share->table_name.length + 1); + + m_table->init_one_table(db, share->db.length, table_name, + share->table_name.length, alias, + TL_WRITE_CONCURRENT_INSERT, MDL_SHARED_WRITE); + m_table->open_strategy = TABLE_LIST::OPEN_IF_EXISTS; + m_table->open_type = OT_BASE_ONLY; + m_table->sequence_scan.set(Sequence_scan::ORIGINAL_SCAN); + } + + /** The cloned TABLE_LIST will be free when statement end. */ + ~Table_list_state() {} + + TABLE_LIST *cloned_table() const { return m_table; } + + private: + THD *thd; + TABLE_LIST *m_table; + }; + + Open_sequence_table_ctx(THD *thd, TABLE_LIST *table_list); + + Open_sequence_table_ctx(THD *thd, TABLE_SHARE *share); + + virtual ~Open_sequence_table_ctx(); + /** + Open and lock the sequence table. + + @retval false success + @retval true failure + */ + bool open_table(); + /** + Get the TABLE object + + @retval table TABLE object + */ + TABLE *get_table() const { + if (m_inherit_table && m_inherit_table->table) + return m_inherit_table->table; + else + return m_state.cloned_table()->table; + } + + private: + THD *m_thd; + TABLE_LIST *m_inherit_table; + Table_list_state m_state; +}; + +/** + When CREATE SQUENCE, beside of creating table structure, also need to insert + initial row into table. +*/ +class Insert_sequence_table_ctx { + public: + Insert_sequence_table_ctx(THD *thd, TABLE_LIST *table_list, + const Sequence_info *seq_info); + virtual ~Insert_sequence_table_ctx(); + /** + Write the sequence initial row. + + @retval false success + @retval true failure + */ + bool write_record(); + + private: + Open_sequence_table_ctx otx; + THD *m_thd; + const Sequence_info *m_seq_info; + bool m_save_binlog_row_based; +}; + +#endif diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 97f587e49daf..a17ffaafd47e 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -98,6 +98,7 @@ #include "sql/field.h" #include "sql/filesort.h" // Filesort #include "sql/gis/srid.h" +#include "sql/ha_sequence.h" // get_ha_sequence #include "sql/handler.h" #include "sql/histograms/histogram.h" #include "sql/item.h" @@ -139,6 +140,7 @@ #include "sql/sql_plist.h" #include "sql/sql_plugin_ref.h" #include "sql/sql_resolver.h" // setup_order +#include "sql/sql_sequence.h" // Insert_sequence_table_ctx #include "sql/sql_show.h" #include "sql/sql_tablespace.h" // validate_tablespace_name #include "sql/sql_time.h" // make_truncated_value_warning @@ -988,9 +990,9 @@ static bool rea_create_base_table( } if ((create_info->db_type->flags & HTON_SUPPORTS_ATOMIC_DDL) && - create_info->db_type->post_ddl) + create_info->db_type->post_ddl) { *post_ddl_ht = create_info->db_type; - + } if (ha_create_table(thd, path, db, table_name, create_info, false, false, table_def)) { /* @@ -7756,6 +7758,30 @@ static bool create_table_impl( } } + /** + Strictly check the columns format if CREATE TABLE .... ENGINE= sequence; + */ + if ((create_info->db_type == + ha_resolve_by_legacy_type(thd, DB_TYPE_SEQUENCE_DB)) && + check_sequence_fields_valid(alter_info)) { + my_error(ER_SEQUENCE_INVALID, MYF(0), db, table_name); + DBUG_RETURN(true); + } + + /* Change the table engine to sequence engine if CREATE SEQUENCE ... */ + if (thd->lex->sequence_info) { + Sequence_info *sequence_info = thd->lex->sequence_info; + DBUG_ASSERT(thd->lex->sql_command == SQLCOM_CREATE_TABLE); + DBUG_ASSERT(create_info->db_type != sequence_hton); + file.reset(get_ha_sequence(sequence_info, thd->mem_root)); + if (file.get() == nullptr) { + mem_alloc_error(sizeof(handler)); + DBUG_RETURN(true); + } + create_info->db_type = ha_resolve_by_legacy_type(thd, DB_TYPE_SEQUENCE_DB); + DBUG_ASSERT(create_info->db_type == sequence_hton); + } + /* Suppress key length errors if this is a white listed table. */ Key_length_error_handler error_handler; bool is_whitelisted_table = @@ -8825,6 +8851,12 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table, &uncommitted_tables); } + /* Init the sequence row if CREATE SEQUENCE */ + if (!result && create_info->sequence_info) { + result = Insert_sequence_table_ctx(thd, create_table, + create_info->sequence_info) + .write_record(); + } /* Unless we are executing CREATE TEMPORARY TABLE we need to commit changes to the data-dictionary, SE and binary log and possibly run diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 97f40bf946dd..64019f32ee33 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -108,6 +108,9 @@ Note: YYTHD is passed as an argument to yyparse(), and subsequently to yylex(). // Sql_cmd_create_trigger #include "sql/sql_truncate.h" // Sql_cmd_truncate_table +#include "sql/item_sequence_func.h" //Item_func_nextval, Item_func_currval +#include "sql/sql_sequence.h" //Sql_cmd_create_sequence + /* this is to get the bison compilation windows warnings out */ #ifdef _MSC_VER /* warning C4065: switch statement contains 'default' but no 'case' labels */ @@ -472,8 +475,7 @@ void warn_about_deprecated_national(THD *thd) FUTURE-USE : Reserved for futur use This makes the code grep-able, and helps maintenance. - - 2) About token values +2) About token values Token values are assigned by bison, in order of declaration. @@ -1229,6 +1231,15 @@ void warn_about_deprecated_national(THD *thd) %token SECONDARY_LOAD_SYM /* MYSQL */ %token SECONDARY_UNLOAD_SYM /* MYSQL */ +/* Tokens for Sequence object */ +%token INCREMENT_SYM /* MYSQL */ +%token CYCLE_SYM /* MYSQL */ +%token MINVALUE_SYM /* MYSQL */ +%token NOCACHE_SYM /* MYSQL */ +%token NOCYCLE_SYM /* MYSQL */ +%token SEQUENCE_SYM /* MYSQL */ +%token CURRVAL_SYM /* MYSQL */ +%token NEXTVAL_SYM /* MYSQL */ /* Resolve column attribute ambiguity -- force precedence of "UNIQUE KEY" against @@ -1810,6 +1821,9 @@ void warn_about_deprecated_national(THD *thd) %type opt_create_table_options_etc opt_create_partitioning_etc opt_duplicate_as_qe +%type opt_sequence opt_sequence_options +%type opt_sequence_option + %type opt_wild_or_where_for_show // used by JSON_TABLE %type columns_clause columns_list @@ -2676,8 +2690,76 @@ create_table_stmt: { $$= NEW_PTN PT_create_table_stmt(YYMEM_ROOT, $2, $4, $5, $8); } + | CREATE SEQUENCE_SYM opt_if_not_exists table_ident opt_sequence + { + $$ = NEW_PTN PT_create_sequence_stmt(YYMEM_ROOT, $3, $4, + On_duplicate::ERROR, $5); + } ; +opt_sequence: + /* empty */ { $$= NULL; } + | opt_sequence_options + { + $$= $1; + } + +opt_sequence_options: + opt_sequence_option + { + $$= NEW_PTN Mem_root_array(YYMEM_ROOT); + if ($$ == NULL || $$->push_back($1)) + MYSQL_YYABORT; + } + | opt_sequence_options opt_sequence_option + { + $$= $1; + if ($$->push_back($2)) + MYSQL_YYABORT; + } + +opt_sequence_option: + MINVALUE_SYM ulonglong_num + { + $$= NEW_PTN PT_values_create_sequence_option< + Sequence_field::FIELD_NUM_MINVALUE>($2); + } + | MAX_VALUE_SYM ulonglong_num + { + $$= NEW_PTN PT_values_create_sequence_option< + Sequence_field::FIELD_NUM_MAXVALUE>($2); + } + | START_SYM WITH ulonglong_num + { + $$= NEW_PTN PT_values_create_sequence_option< + Sequence_field::FIELD_NUM_START>($3); + } + | INCREMENT_SYM BY ulonglong_num + { + $$= NEW_PTN PT_values_create_sequence_option< + Sequence_field::FIELD_NUM_INCREMENT>($3); + } + | CACHE_SYM ulonglong_num + { + $$= NEW_PTN PT_values_create_sequence_option< + Sequence_field::FIELD_NUM_CACHE>($2); + } + | NOCACHE_SYM + { + $$= NEW_PTN PT_values_create_sequence_option< + Sequence_field::FIELD_NUM_CACHE>(0); + } + | CYCLE_SYM + { + $$= NEW_PTN PT_values_create_sequence_option< + Sequence_field::FIELD_NUM_CYCLE>(1); + } + | NOCYCLE_SYM + { + $$= NEW_PTN PT_values_create_sequence_option< + Sequence_field::FIELD_NUM_CYCLE>(0); + } + create_role_stmt: CREATE ROLE_SYM opt_if_not_exists role_list { @@ -9417,6 +9499,31 @@ simple_expr: { $$= NEW_PTN Item_date_add_interval(@$, $5, $2, $3, 0); } + | NEXTVAL_SYM '(' table_ident ')' + { + TABLE_LIST *table; + LEX *lex= Lex; + if (!(table = lex->current_select()->add_table_to_list( + lex->thd, $3, NULL, TL_OPTION_SEQUENCE, TL_READ, + MDL_SHARED_READ, NULL, NULL, NULL, NULL, + Sequence_scan_mode::ITERATION_SCAN))) + MYSQL_YYABORT; + $$ = NEW_PTN Item_func_nextval(YYTHD, table); + + } + | CURRVAL_SYM '(' table_ident ')' + { + TABLE_LIST *table; + LEX *lex= Lex; + if (!(table = lex->current_select()->add_table_to_list( + lex->thd, $3, NULL, TL_OPTION_SEQUENCE, TL_READ, + MDL_SHARED_READ, NULL, NULL, NULL, NULL, + Sequence_scan_mode::ITERATION_SCAN))) + MYSQL_YYABORT; + + $$= NEW_PTN Item_func_currval(YYTHD, table); + + } | simple_ident JSON_SEPARATOR_SYM TEXT_STRING_literal { Item_string *path= @@ -13877,6 +13984,8 @@ role_or_label_keyword: */ | CURRENT_SYM | CURSOR_NAME_SYM + | CURRVAL_SYM + | CYCLE_SYM | DATAFILE_SYM | DATA_SYM | DATE_SYM @@ -13935,6 +14044,7 @@ role_or_label_keyword: | HOUR_SYM | IDENTIFIED_SYM | IGNORE_SERVER_IDS_SYM + | INCREMENT_SYM | INDEXES | INITIAL_SIZE_SYM | INSERT_METHOD @@ -13994,6 +14104,7 @@ role_or_label_keyword: | MIGRATE_SYM | MIN_ROWS | MINUTE_SYM + | MINVALUE_SYM | MODE_SYM | MODIFY_SYM | MONTH_SYM @@ -14011,6 +14122,9 @@ role_or_label_keyword: | NEVER_SYM | NEW_SYM | NEXT_SYM + | NEXTVAL_SYM + | NOCACHE_SYM + | NOCYCLE_SYM | NODEGROUP_SYM | NO_WAIT_SYM | NOWAIT_SYM @@ -14084,6 +14198,7 @@ role_or_label_keyword: | SCHEDULE_SYM | SCHEMA_NAME_SYM | SECOND_SYM + | SEQUENCE_SYM | SERIALIZABLE_SYM | SERIAL_SYM | SESSION_SYM @@ -14534,6 +14649,7 @@ lock: table_or_tables: TABLE_SYM | TABLES + | SEQUENCE_SYM ; table_lock_list: diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 5eab3ebf5a85..51097cd116d3 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -6196,3 +6196,9 @@ static Sys_var_bool Sys_sql_require_primary_key{ NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_has_super)}; + +static Sys_var_bool Sys_sequence_read_skip_cache( + "sequence_read_skip_cache", + "Skip sequence cache, read the based table directly.", + SESSION_ONLY(sequence_read_skip_cache), NO_CMD_LINE, DEFAULT(FALSE), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0)); diff --git a/sql/system_variables.h b/sql/system_variables.h index f3b3234cbe33..ddb24194aeaa 100644 --- a/sql/system_variables.h +++ b/sql/system_variables.h @@ -352,6 +352,8 @@ struct System_variables { ulong use_secondary_engine; bool sql_require_primary_key; + + bool sequence_read_skip_cache; }; /** diff --git a/sql/table.cc b/sql/table.cc index 4eb957f28c54..40659dc8127f 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -419,6 +419,9 @@ TABLE_SHARE *alloc_table_share(const char *db, const char *table_name, table_cache_instances * sizeof(*cache_element_array)); share->cache_element = cache_element_array; + /** Must destruct it without free */ + share->sequence_property = new (&mem_root) Sequence_property(); + share->mem_root = std::move(mem_root); mysql_mutex_init(key_TABLE_SHARE_LOCK_ha_data, &share->LOCK_ha_data, MY_MUTEX_INIT_FAST); @@ -485,6 +488,8 @@ void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key, share->m_flush_tickets.empty(); + share->sequence_property = new (&share->mem_root) Sequence_property(); + DBUG_VOID_RETURN; } @@ -570,6 +575,7 @@ void TABLE_SHARE::destroy() { #ifdef HAVE_PSI_TABLE_INTERFACE PSI_TABLE_CALL(release_table_share)(m_psi); #endif + ::destroy(sequence_property); /* Make a copy since the share is allocated in its own root, @@ -4013,6 +4019,12 @@ void TABLE::init(THD *thd, TABLE_LIST *tl) { bool error MY_ATTRIBUTE((unused)) = refix_value_generator_items(thd); DBUG_ASSERT(!error); } + + /** + Inherit the sequence scan mode every TABLE::init() from TABLE_LIST + for each statement. + */ + sequence_scan = tl->sequence_scan; } /** diff --git a/sql/table.h b/sql/table.h index 64e12ce17121..224d2560652c 100644 --- a/sql/table.h +++ b/sql/table.h @@ -61,6 +61,8 @@ #include "sql/mem_root_array.h" +#include "sql/sequence_common.h" // Sequence_property, Sequence_scan, Sequence_last_value + class Field; namespace histograms { @@ -106,6 +108,7 @@ struct TABLE_LIST; struct TABLE_SHARE; struct handlerton; typedef int8 plan_idx; +class Sequence_property; namespace dd { class Table; @@ -1148,6 +1151,10 @@ struct TABLE_SHARE { /// Does this TABLE_SHARE represent a table in a secondary storage engine? bool m_secondary{false}; + + public: + /** Sequence attributes represent that it is sequence table */ + Sequence_property *sequence_property; }; /** @@ -2109,6 +2116,11 @@ struct TABLE { set or not */ bool should_binlog_drop_if_temp(void) const; + /** + Sequence scan mode only affect one table but not all query lex, + so We define this option within TABLE object. + */ + Sequence_scan sequence_scan; }; static inline void empty_record(TABLE *table) { @@ -3399,6 +3411,10 @@ struct TABLE_LIST { enum_table_ref_type m_table_ref_type{TABLE_REF_NULL}; /** See comments for TABLE_SHARE::get_table_ref_version() */ ulonglong m_table_ref_version{0}; + + public: + /** Represent the sequence query scan mode */ + Sequence_scan sequence_scan; }; /* diff --git a/sql/udf_example.cc b/sql/udf_example.cc index 4a5e5abb783a..3cb4d608df04 100644 --- a/sql/udf_example.cc +++ b/sql/udf_example.cc @@ -62,7 +62,7 @@ ** ** Function 'myfunc_int' returns summary length of all its arguments. ** -** Function 'sequence' returns an sequence starting from a certain number. +** Function 'sequence_alias' returns an sequence starting from a certain number. ** ** Function 'myfunc_argument_name' returns name of argument. ** @@ -86,7 +86,7 @@ ** CREATE FUNCTION metaphon RETURNS STRING SONAME "udf_example.so"; ** CREATE FUNCTION myfunc_double RETURNS REAL SONAME "udf_example.so"; ** CREATE FUNCTION myfunc_int RETURNS INTEGER SONAME "udf_example.so"; -** CREATE FUNCTION sequence RETURNS INTEGER SONAME "udf_example.so"; +** CREATE FUNCTION sequence_alias RETURNS INTEGER SONAME "udf_example.so"; ** CREATE FUNCTION lookup RETURNS STRING SONAME "udf_example.so"; ** CREATE FUNCTION reverse_lookup RETURNS STRING SONAME "udf_example.so"; ** CREATE AGGREGATE FUNCTION avgcost RETURNS REAL SONAME "udf_example.so"; @@ -537,7 +537,7 @@ extern "C" bool myfunc_int_init(UDF_INIT *, UDF_ARGS *, char *) { return 0; } or 1 if no arguments have been given */ -extern "C" bool sequence_init(UDF_INIT *initid, UDF_ARGS *args, char *message) { +extern "C" bool sequence_alias_init(UDF_INIT *initid, UDF_ARGS *args, char *message) { if (args->arg_count > 1) { strcpy(message, "This function takes none or 1 argument"); return 1; @@ -551,18 +551,18 @@ extern "C" bool sequence_init(UDF_INIT *initid, UDF_ARGS *args, char *message) { } memset(initid->ptr, 0, sizeof(long long)); /* - sequence() is a non-deterministic function : it has different value + sequence_alias() is a non-deterministic function : it has different value even if called with the same arguments. */ initid->const_item = 0; return 0; } -extern "C" void sequence_deinit(UDF_INIT *initid) { +extern "C" void sequence_alias_deinit(UDF_INIT *initid) { if (initid->ptr) free(initid->ptr); } -extern "C" long long sequence(UDF_INIT *initid, UDF_ARGS *args, unsigned char *, +extern "C" long long sequence_alias(UDF_INIT *initid, UDF_ARGS *args, unsigned char *, unsigned char *) { unsigned long long val = 0; if (args->arg_count) val = *((long long *)args->args[0]); diff --git a/sql/udf_example.def b/sql/udf_example.def index 3960076eb754..59b1f9933078 100644 --- a/sql/udf_example.def +++ b/sql/udf_example.def @@ -33,9 +33,9 @@ EXPORTS myfunc_double myfunc_int_init myfunc_int - sequence_init - sequence_deinit - sequence + sequence_alias_init + sequence_alias_deinit + sequence_alias avgcost_init avgcost_deinit avgcost_add