diff --git a/1-sys_guid.md b/1-sys_guid.md new file mode 100644 index 0000000000000000000000000000000000000000..bd233caa85cb630c2b58b43b698b0f0f4b6949fc --- /dev/null +++ b/1-sys_guid.md @@ -0,0 +1,36 @@ +### Oracle兼容函数 sys_guid() + +------ +#### 语法 + +``` +sys_guid() +``` + +#### 定义和用法 +系统根据当前时间和机器码,生成全球唯一的一个32位序列号,32个字符(8,4,4,4,12),前三段(8,4,4)为依据当前时间生成,后两段(4,12)为机器码生成,函数返回数值为长度为32位的字符串,包括0-9和小写a-f + +#### Oracle兼容程度说明 + +MySQL sys_guid产生的序列号意义与Oracle一致,生成唯一的序列号,都是32字符,差异:sys_guid 返回的32位随机数中间包括一些小写的英文字母, +而oarcle返回中间则为包含大写的英文字母;Oracle不同版本返回值类型存在不同,如返回类型为raw。 + +#### 示例 + +``` +mysql> SELECT sys_guid() ; ++----------------------------------+ +| sys_guid() | ++----------------------------------+ +| 9f5bed5ea47611ed9b0a0242ac110002 | ++----------------------------------+ +1 row in set (0.00 sec) + +mysql> SELECT sys_guid() ; ++----------------------------------+ +| sys_guid() | ++----------------------------------+ +| a0832c47a47611ed9b0a0242ac110002 | ++----------------------------------+ +1 row in set (0.00 sec) +``` \ No newline at end of file diff --git a/1-sys_guid.patch b/1-sys_guid.patch new file mode 100644 index 0000000000000000000000000000000000000000..bd3ae5337e2d1553651a6443894650c567f14e3b --- /dev/null +++ b/1-sys_guid.patch @@ -0,0 +1,327 @@ +diff --git a/mysql-test/r/guid.result b/mysql-test/r/guid.result +new file mode 100644 +index 00000000000..0dce87df7a8 +--- /dev/null ++++ b/mysql-test/r/guid.result +@@ -0,0 +1,69 @@ ++CREATE TABLE t1 (iv INT, cv VARCHAR(10), ncv VARCHAR(40) CHARACTER SET UTF8MB4); ++INSERT INTO t1 VALUES (1, '11111', sys_guid()); ++INSERT INTO t1 VALUES (2, '22222', sys_guid()); ++INSERT INTO t1 VALUES (3, '33333', sys_guid()); ++INSERT INTO t1 VALUES (4, '44444', sys_guid()); ++INSERT INTO t1 VALUES (5, '55555', sys_guid()); ++INSERT INTO t1 VALUES (6, '66666', sys_guid()); ++INSERT INTO t1 VALUES (7, '77777', sys_guid()); ++# sys_guid 产生32字符,生成的字符唯一,不能有任何相同 ++SELECT LENGTH (ncv) FROM t1; ++LENGTH (ncv) ++32 ++32 ++32 ++32 ++32 ++32 ++32 ++SELECT COUNT(DISTINCT ncv) FROM t1; ++COUNT(DISTINCT ncv) ++7 ++# sys_guid 产生32字符,如:aaaaaaaabbbbccccddddeeeeeeeeeeee,第五段为依据mac生成,相同库必须一致 ++select COUNT(DISTINCT SUBSTR(ncv,20)) from t1; ++COUNT(DISTINCT SUBSTR(ncv,20)) ++1 ++INSERT INTO t1 VALUES (3, 'greatdb', _UTF8MB4 '万里开源'); ++select length(sys_guid())=32; ++length(sys_guid())=32 ++1 ++select 1 from t1 order by sys_guid(); ++1 ++1 ++1 ++1 ++1 ++1 ++1 ++1 ++1 ++select iv from (select iv,sys_guid() from t1 group by iv,sys_guid()) t2; ++iv ++1 ++2 ++3 ++4 ++5 ++6 ++7 ++3 ++select iv from(select iv,sys_guid(),GROUP_CONCAT(sys_guid() ORDER BY sys_guid() DESC ) from t1 group by iv,sys_guid() order by iv,sys_guid()) t2; ++iv ++1 ++2 ++3 ++3 ++4 ++5 ++6 ++7 ++select sys_guid() is null,sys_guid() is not null; ++sys_guid() is null sys_guid() is not null ++0 1 ++select count(*) from (select 1 from t1 where sys_guid() is null) t2; ++count(*) ++0 ++select count(*) from (select 1 from t1 where sys_guid() is not null) t2; ++count(*) ++8 ++DROP TABLE t1; +diff --git a/mysql-test/t/guid.test b/mysql-test/t/guid.test +new file mode 100644 +index 00000000000..92faac83ca9 +--- /dev/null ++++ b/mysql-test/t/guid.test +@@ -0,0 +1,37 @@ ++ ++# 'ncv NVARCHAR(10)' triggers warning in 8.0.22. ++# Using VARCHAR(40) CHARACTER SET UTF8MB4 to avoid warning. ++CREATE TABLE t1 (iv INT, cv VARCHAR(10), ncv VARCHAR(40) CHARACTER SET UTF8MB4); ++INSERT INTO t1 VALUES (1, '11111', sys_guid()); ++INSERT INTO t1 VALUES (2, '22222', sys_guid()); ++INSERT INTO t1 VALUES (3, '33333', sys_guid()); ++INSERT INTO t1 VALUES (4, '44444', sys_guid()); ++INSERT INTO t1 VALUES (5, '55555', sys_guid()); ++INSERT INTO t1 VALUES (6, '66666', sys_guid()); ++INSERT INTO t1 VALUES (7, '77777', sys_guid()); ++ ++--echo # sys_guid 产生32字符,生成的字符唯一,不能有任何相同 ++SELECT LENGTH (ncv) FROM t1; ++SELECT COUNT(DISTINCT ncv) FROM t1; ++ ++--echo # sys_guid 产生32字符,如:aaaaaaaabbbbccccddddeeeeeeeeeeee,第五段为依据mac生成,相同库必须一致 ++select COUNT(DISTINCT SUBSTR(ncv,20)) from t1; ++ ++ ++# n'万里开源' triggers warning in 8.0.22. ++# Specifying UTF8MB4 to avoid warning. ++INSERT INTO t1 VALUES (3, 'greatdb', _UTF8MB4 '万里开源'); ++ ++#add test case ++select length(sys_guid())=32; ++ ++select 1 from t1 order by sys_guid(); ++select iv from (select iv,sys_guid() from t1 group by iv,sys_guid()) t2; ++select iv from(select iv,sys_guid(),GROUP_CONCAT(sys_guid() ORDER BY sys_guid() DESC ) from t1 group by iv,sys_guid() order by iv,sys_guid()) t2; ++select sys_guid() is null,sys_guid() is not null; ++select count(*) from (select 1 from t1 where sys_guid() is null) t2; ++select count(*) from (select 1 from t1 where sys_guid() is not null) t2; ++ ++DROP TABLE t1; ++ ++ +diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc +index c494eabb65e..43fea7ca38a 100644 +--- a/sql/item_strfunc.cc ++++ b/sql/item_strfunc.cc +@@ -4206,6 +4206,134 @@ String *Item_func_uuid::val_str(String *str) { + return mysql_generate_uuid(str); + } + ++bool Item_func_sys_guid::resolve_type(THD *) { ++ collation.set(system_charset_info, DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII); ++ set_data_type_string(uint32(UUID_LENGTH - 4)); ++ return false; ++} ++ ++bool Item_func_sys_guid::itemize(Parse_context *pc, Item **res) { ++ if (skip_itemize(res)) return false; ++ if (super::itemize(pc, res)) return true; ++ pc->thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_SYSTEM_FUNCTION); ++ pc->thd->lex->safe_to_cache_query = false; ++ return false; ++} ++ ++String *mysql_generate_sys_guid(String *str) { ++ char *s; ++ THD *thd = current_thd; ++ ++ mysql_mutex_lock(&LOCK_uuid_generator); ++ if (!uuid_time) /* first UUID() call. initializing data */ ++ { ++ ulong tmp = sql_rnd_with_mutex(); ++ uchar mac[6]; ++ int i; ++ if (my_gethwaddr(mac)) { ++ /* purecov: begin inspected */ ++ /* ++ generating random "hardware addr" ++ and because specs explicitly specify that it should NOT correlate ++ with a clock_seq value (initialized random below), we use a separate ++ randominit() here ++ */ ++ randominit(&uuid_rand, tmp + (ulong)thd, ++ tmp + (ulong)atomic_global_query_id); ++ for (i = 0; i < (int)sizeof(mac); i++) ++ mac[i] = (uchar)(my_rnd(&uuid_rand) * 255); ++ /* purecov: end */ ++ } ++ s = clock_seq_and_node_str + sizeof(clock_seq_and_node_str) - 1; ++ for (i = sizeof(mac) - 1; i >= 0; i--) { ++ *--s = _dig_vec_lower[mac[i] & 15]; ++ *--s = _dig_vec_lower[mac[i] >> 4]; ++ } ++ randominit(&uuid_rand, tmp + (ulong)server_start_time, ++ tmp + (ulong)thd->status_var.bytes_sent); ++ set_clock_seq_str(); ++ } ++ ++ ulonglong tv = my_getsystime() + UUID_TIME_OFFSET + nanoseq; ++ ++ if (likely(tv > uuid_time)) { ++ /* ++ Current time is ahead of last timestamp, as it should be. ++ If we "borrowed time", give it back, just as long as we ++ stay ahead of the previous timestamp. ++ */ ++ if (nanoseq) { ++ assert((tv > uuid_time) && (nanoseq > 0)); ++ /* ++ -1 so we won't make tv= uuid_time for nanoseq >= (tv - uuid_time) ++ */ ++ ulong delta = min(nanoseq, (ulong)(tv - uuid_time - 1)); ++ tv -= delta; ++ nanoseq -= delta; ++ } ++ } else { ++ if (unlikely(tv == uuid_time)) { ++ /* ++ For low-res system clocks. If several requests for UUIDs ++ end up on the same tick, we add a nano-second to make them ++ different. ++ ( current_timestamp + nanoseq * calls_in_this_period ) ++ may end up > next_timestamp; this is OK. Nonetheless, we'll ++ try to unwind nanoseq when we get a chance to. ++ If nanoseq overflows, we'll start over with a new numberspace ++ (so the if() below is needed so we can avoid the ++tv and thus ++ match the follow-up if() if nanoseq overflows!). ++ */ ++ if (likely(++nanoseq)) ++tv; ++ } ++ ++ if (unlikely(tv <= uuid_time)) { ++ /* ++ If the admin changes the system clock (or due to Daylight ++ Saving Time), the system clock may be turned *back* so we ++ go through a period once more for which we already gave out ++ UUIDs. To avoid duplicate UUIDs despite potentially identical ++ times, we make a new random component. ++ We also come here if the nanoseq "borrowing" overflows. ++ In either case, we throw away any nanoseq borrowing since it's ++ irrelevant in the new numberspace. ++ */ ++ set_clock_seq_str(); ++ tv = my_getsystime() + UUID_TIME_OFFSET; ++ nanoseq = 0; ++ DBUG_PRINT("uuid", ("making new numberspace")); ++ } ++ } ++ ++ uuid_time = tv; ++ mysql_mutex_unlock(&LOCK_uuid_generator); ++ ++ uint32 time_low = (uint32)(tv & 0xFFFFFFFF); ++ uint16 time_mid = (uint16)((tv >> 32) & 0xFFFF); ++ uint16 time_hi_and_version = (uint16)((tv >> 48) | UUID_VERSION); ++ ++ str->mem_realloc(UUID_LENGTH + 1 - 4); ++ str->length(UUID_LENGTH - 4); ++ str->set_charset(system_charset_info); ++ s = str->ptr(); ++ // s[8] = s[13] = '-'; ++ tohex(s, time_low, 8); ++ tohex(s + 8, time_mid, 4); ++ tohex(s + 12, time_hi_and_version, 4); ++ int j, k = 16; ++ for (j = 0; clock_seq_and_node_str[j] != '\0'; j++) ++ if (clock_seq_and_node_str[j] != '-') s[k++] = clock_seq_and_node_str[j]; ++ ++ DBUG_EXECUTE_IF("force_fake_guid", ++ my_stpcpy(s, "a2d00942b69c11e4a6960020ff6fcbe6");); ++ return str; ++} ++ ++String *Item_func_sys_guid::val_str(String *str) { ++ assert(fixed == 1); ++ return mysql_generate_sys_guid(str); ++} ++ + bool Item_func_gtid_subtract::resolve_type(THD *thd) { + if (param_type_is_default(thd, 0, -1)) return true; + set_nullable(args[0]->is_nullable() || args[1]->is_nullable()); +diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h +index e4a8506a7cb..4424a6f136c 100644 +--- a/sql/item_strfunc.h ++++ b/sql/item_strfunc.h +@@ -1303,6 +1303,30 @@ class Item_func_uuid final : public Item_str_func { + Item *pq_clone(THD *thd, Query_block *select) override; + }; + ++class Item_func_sys_guid final : public Item_str_func { ++ typedef Item_str_func super; ++ ++ public: ++ Item_func_sys_guid() : Item_str_func() {} ++ explicit Item_func_sys_guid(const POS &pos) : Item_str_func(pos) {} ++ ++ bool itemize(Parse_context *pc, Item **res) override; ++ table_map get_initial_pseudo_tables() const override { ++ return RAND_TABLE_BIT; ++ } ++ bool resolve_type(THD *) override; ++ const char *func_name() const override { return "sys_guid"; } ++ String *val_str(String *) override; ++ bool check_function_as_value_generator(uchar *checker_args) override { ++ Check_function_as_value_generator_parameters *func_arg = ++ pointer_cast( ++ checker_args); ++ func_arg->banned_function_name = func_name(); ++ return ((func_arg->source == VGS_GENERATED_COLUMN) || ++ (func_arg->source == VGS_CHECK_CONSTRAINT)); ++ } ++}; ++ + class Item_func_gtid_subtract final : public Item_str_ascii_func { + String buf1, buf2; + +diff --git a/sql/lex.h b/sql/lex.h +index e14e76f6963..a7251ee2968 100644 +--- a/sql/lex.h ++++ b/sql/lex.h +@@ -863,6 +863,7 @@ static const SYMBOL symbols[] = { + {SYM_FN("ST_COLLECT", ST_COLLECT_SYM)}, + {SYM_FN("SUBDATE", SUBDATE_SYM)}, + {SYM_FN("SUBSTR", SUBSTRING)}, ++ {SYM_FN("SYS_GUID", SYS_GUID_SYM)}, + {SYM_FN("SUBSTRING", SUBSTRING)}, + {SYM_FN("SUM", SUM_SYM)}, + {SYM_FN("SYSDATE", SYSDATE)}, +diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy +index 2b6bc52bafc..35aaab16c2b 100644 +--- a/sql/sql_yacc.yy ++++ b/sql/sql_yacc.yy +@@ -1376,6 +1376,7 @@ void warn_about_deprecated_binary(THD *thd) + */ + %token EFFECTIVE_SYM 1350 + %token SEQUENCE_TABLE_SYM 1351 ++%token SYS_GUID_SYM 1352 + + /* + Precedence rules used to resolve the ambiguity when using keywords as idents +@@ -10892,6 +10893,10 @@ function_call_nonkeyword: + $$= NEW_PTN PTI_function_call_nonkeyword_sysdate(@$, + static_cast($2)); + } ++ | SYS_GUID_SYM '(' ')' ++ { ++ $$= NEW_PTN Item_func_sys_guid(@$); ++ } + | TIMESTAMP_ADD '(' interval_time_stamp ',' expr ',' expr ')' + { + $$= NEW_PTN Item_date_add_interval(@$, $7, $5, $3, 0); \ No newline at end of file diff --git a/2-decode.md b/2-decode.md new file mode 100644 index 0000000000000000000000000000000000000000..a7290245bd866ea7221557362c0bbaf5866b7fe3 --- /dev/null +++ b/2-decode.md @@ -0,0 +1,37 @@ +### Oracle兼容函数 decode() + +------ + +#### 语法 + +```sql +decode( + expr, /* 字段或表达式 */ + search, /* 可能值 */ + result /* 返回值 */ + [, search, result ]... /* 可选可能值/返回值对[允许多个] */ + [, default ] /* 可选无匹配返回值 */ +) +``` + +#### 定义和用法 + +比较`expr`和`search`的结果,如果一致则返回对应的`result`,如果所有条件都不匹配则返回`default`,如果`default`不存在,则返回NULL。 + +对于`expr=search=NULL`,的情况与Oracle行为一致,会返回第一个`search`为NULL的`result`。 + +注意: + +- 可能值与返回值的评估使用`短路评估`,即:只要存在`expr`=`search`则后续`search`不会被评估 + + +#### 示例 + +```sql +> SELECT decode(1, 1, 'one', 2, 'two', 'other'); ++----------------------------------------+ +| decode(1, 1, 'one', 2, 'two', 'other') | ++----------------------------------------+ +| one | ++----------------------------------------+ +``` \ No newline at end of file diff --git a/2-decode.patch b/2-decode.patch new file mode 100644 index 0000000000000000000000000000000000000000..c947fb192386f86a11b4376f163c254841d23ab1 --- /dev/null +++ b/2-decode.patch @@ -0,0 +1,565 @@ +diff --git a/mysql-test/r/decode.result b/mysql-test/r/decode.result +new file mode 100644 +index 00000000000..84e8b991498 +--- /dev/null ++++ b/mysql-test/r/decode.result +@@ -0,0 +1,226 @@ ++drop table if exists t1, t2; ++SET sql_mode = 'NO_ENGINE_SUBSTITUTION'; ++select DECODE("b", "a", 1, "b", 2); ++DECODE("b", "a", 1, "b", 2) ++2 ++select DECODE("c", "a", 1, "b", 2); ++DECODE("c", "a", 1, "b", 2) ++NULL ++select DECODE("c", "a", 1, "b", 2, 3); ++DECODE("c", "a", 1, "b", 2, 3) ++3 ++select DECODE(BINARY "b", "a", 1, "B", 2, "b", "ok"); ++DECODE(BINARY "b", "a", 1, "B", 2, "b", "ok") ++ok ++select DECODE("b", "a", 1, "B", 2, "b", "ok"); ++DECODE("b", "a", 1, "B", 2, "b", "ok") ++2 ++select DECODE(concat("a","b"), concat("ab",""), "a", "b", "b"); ++DECODE(concat("a","b"), concat("ab",""), "a", "b", "b") ++a ++select DECODE(1=0, false, "true", "false"); ++DECODE(1=0, false, "true", "false") ++true ++select DECODE(1, 1, "one", 2, "two", "more"); ++DECODE(1, 1, "one", 2, "two", "more") ++one ++explain select DECODE(1, 1, "one", 2, "two", "more"); ++id select_type table partitions type possible_keys key key_len ref rows filtered Extra ++1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL NULL No tables used ++Warnings: ++Note 1003 /* select#1 */ select decode(1,1,'one',2,'two','more') AS `DECODE(1, 1, "one", 2, "two", "more")` ++select DECODE(2.0, 1, "one", 2.0, "two", "more"); ++DECODE(2.0, 1, "one", 2.0, "two", "more") ++two ++select DECODE(1, 1, true, 1, false) from dual; ++DECODE(1, 1, true, 1, false) ++1 ++select DECODE("two", "one", "1", "two", "2") | 0; ++DECODE("two", "one", "1", "two", "2") | 0 ++2 ++select DECODE("two","one", 1.00, "two", 2.00) +0.0; ++DECODE("two","one", 1.00, "two", 2.00) +0.0 ++2.00 ++select DECODE(1/0, "a", "true", "false"); ++DECODE(1/0, "a", "true", "false") ++false ++select DECODE(1/0, "a", "true"); ++DECODE(1/0, "a", "true") ++NULL ++select DECODE(1/0, "a", "true") | 0; ++DECODE(1/0, "a", "true") | 0 ++NULL ++select DECODE(1/0, "a", "true") + 0.0; ++DECODE(1/0, "a", "true") + 0.0 ++NULL ++select DECODE(1>0, true, "TRUE", "FALSE"); ++DECODE(1>0, true, "TRUE", "FALSE") ++TRUE ++select DECODE(1<0, false, "TRUE", "FALSE"); ++DECODE(1<0, false, "TRUE", "FALSE") ++TRUE ++create table t1 (a int); ++insert into t1 values(1),(2),(3),(4); ++select decode(a, 1, 2, 2, 3, 0) as fcase, count(*) from t1 group by fcase; ++fcase count(*) ++2 1 ++3 1 ++0 2 ++analyze table t1; ++Table Op Msg_type Msg_text ++test.t1 analyze status OK ++explain select decode(a, 1, 2, 2, 3, 0) as fcase, count(*) from t1 group by fcase; ++id select_type table partitions type possible_keys key key_len ref rows filtered Extra ++1 SIMPLE t1 NULL ALL NULL NULL NULL NULL 4 100.00 Using temporary ++Warnings: ++Note 1003 /* select#1 */ select decode(`test`.`t1`.`a`,1,2,2,3,0) AS `fcase`,count(0) AS `count(*)` from `test`.`t1` group by `fcase` ++select decode(a, 1, "one", 2, "two", "nothing") as fcase, count(*) from t1 group by fcase; ++fcase count(*) ++one 1 ++two 1 ++nothing 2 ++drop table t1; ++create table t1 (`row` int not null, col int not null, val varchar(255) not null); ++insert into t1 values (1,1,'orange'),(1,2,'large'),(2,1,'yellow'),(2,2,'medium'),(3,1,'green'),(3,2,'small'); ++select max(decode(col, 1, val, null)) as color from t1 group by `row`; ++color ++orange ++yellow ++green ++drop table t1; ++SET NAMES latin1; ++CREATE TABLE t1 SELECT ++DECODE(1, true, _latin1'a' COLLATE latin1_danish_ci, _latin1'a') AS c1, ++DECODE(1, true, _latin1'a', _latin1'a' COLLATE latin1_danish_ci) AS c2, ++DECODE(1, true, 'a', 1) AS c3, ++DECODE(1, true, 1, 'a') AS c4, ++DECODE(1, true, 'a', 1.0) AS c5, ++DECODE(1, true, 1.0, 'a') AS c6, ++DECODE(1, true, 1, 1.0) AS c7, ++DECODE(1, true, 1.0, 1) AS c8, ++DECODE(1, true, 1.0) AS c9, ++DECODE(1, true, 0.1e1, 0.1) AS c10, ++DECODE(1, true, 0.1e1, 1) AS c11, ++DECODE(1, true, 0.1e1, '1') AS c12 ++; ++SHOW CREATE TABLE t1; ++Table Create Table ++t1 CREATE TABLE `t1` ( ++ `c1` varchar(1) CHARACTER SET latin1 COLLATE latin1_danish_ci NOT NULL DEFAULT '', ++ `c2` varchar(1) CHARACTER SET latin1 COLLATE latin1_danish_ci NOT NULL DEFAULT '', ++ `c3` varchar(1) CHARACTER SET latin1 NOT NULL DEFAULT '', ++ `c4` varchar(1) CHARACTER SET latin1 NOT NULL DEFAULT '', ++ `c5` varchar(4) CHARACTER SET latin1 NOT NULL DEFAULT '', ++ `c6` varchar(4) CHARACTER SET latin1 NOT NULL DEFAULT '', ++ `c7` decimal(2,1) NOT NULL DEFAULT '0.0', ++ `c8` decimal(2,1) NOT NULL DEFAULT '0.0', ++ `c9` decimal(2,1) DEFAULT NULL, ++ `c10` double NOT NULL DEFAULT '0', ++ `c11` double NOT NULL DEFAULT '0', ++ `c12` varchar(22) CHARACTER SET latin1 NOT NULL DEFAULT '' ++) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ++DROP TABLE t1; ++SELECT DECODE(1, true, _latin1'a' COLLATE latin1_danish_ci, _latin1'a' COLLATE latin1_swedish_ci); ++ERROR HY000: Illegal mix of collations (latin1_danish_ci,EXPLICIT) and (latin1_swedish_ci,EXPLICIT) for operation 'case' ++SELECT DECODE(_latin1'a' COLLATE latin1_general_ci, ++_latin1'a' COLLATE latin1_danish_ci, 1, ++_latin1'a' COLLATE latin1_swedish_ci, 2 ++); ++ERROR HY000: Illegal mix of collations (latin1_general_ci,EXPLICIT), (latin1_danish_ci,EXPLICIT), (latin1_swedish_ci,EXPLICIT) for operation 'case' ++SELECT ++DECODE(_latin1'a' COLLATE latin1_general_ci, _latin1'A', '1', 2), ++DECODE(_latin1'a' COLLATE latin1_bin, _latin1'A', '1', 2), ++DECODE(_latin1'a', _latin1'A' COLLATE latin1_swedish_ci, '1', 2), ++DECODE(_latin1'a', _latin1'A' COLLATE latin1_bin, '1', 2); ++DECODE(_latin1'a' COLLATE latin1_general_ci, _latin1'A', '1', 2) DECODE(_latin1'a' COLLATE latin1_bin, _latin1'A', '1', 2) DECODE(_latin1'a', _latin1'A' COLLATE latin1_swedish_ci, '1', 2) DECODE(_latin1'a', _latin1'A' COLLATE latin1_bin, '1', 2) ++1 2 1 2 ++SELECT 'decode+union+test' ++UNION ++SELECT DECODE(LOWER('1'), LOWER('2'), 'BUG', 'nobug'); ++decode+union+test ++decode+union+test ++nobug ++SELECT DECODE(LOWER('1'), LOWER('2'), 'BUG', 'nobug'); ++DECODE(LOWER('1'), LOWER('2'), 'BUG', 'nobug') ++nobug ++create table t1(a float, b int default 3); ++insert into t1 (a) values (2), (11), (8); ++select min(a), min(decode(1=1, true, a, NULL)), ++min(decode(1!=1, true, NULL, a)) ++from t1 where b=3 group by b; ++min(a) min(decode(1=1, true, a, NULL)) min(decode(1!=1, true, NULL, a)) ++2 2 2 ++drop table t1; ++create table t1 (a int, b bigint unsigned); ++create table t2 (c int); ++insert into t1 (a, b) values (1,4572794622775114594), (2,18196094287899841997), ++(3,11120436154190595086); ++insert into t2 (c) values (1), (2), (3); ++select t1.a, decode(t1.a, 0, 0, t1.b) d from t1 ++join t2 on t1.a=t2.c order by d; ++a d ++1 4572794622775114594 ++3 11120436154190595086 ++2 18196094287899841997 ++select t1.a, decode(t1.a, 0, 0, t1.b) d from t1 ++join t2 on t1.a=t2.c where b=11120436154190595086 order by d; ++a d ++3 11120436154190595086 ++drop table t1, t2; ++CREATE TABLE t1(a YEAR); ++SELECT 1 FROM t1 WHERE a=1 AND DECODE(1, a, 1, 1); ++1 ++DROP TABLE t1; ++SET sql_mode = default; ++CREATE TABLE source(bt INTEGER, bf INTEGER, i8u BIGINT UNSIGNED, i8s BIGINT); ++INSERT INTO source VALUES ++(1,0,0,-9223372036854775808), (1,0,18446744073709551615,9223372036854775807); ++CREATE TABLE target ++SELECT DECODE(bt, i8u, i8s) as u, ++DECODE(bf, i8u, i8s) as s ++FROM source; ++SHOW CREATE TABLE target; ++Table Create Table ++target CREATE TABLE `target` ( ++ `u` bigint DEFAULT NULL, ++ `s` bigint DEFAULT NULL ++) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ++SELECT DECODE(bt, true, i8u, i8s) AS u, ++DECODE(bf, true, i8u, i8s) AS s ++FROM source; ++u s ++0 -9223372036854775808 ++18446744073709551615 9223372036854775807 ++SELECT * FROM target; ++u s ++NULL -9223372036854775808 ++NULL NULL ++DROP TABLE target; ++# ++# Test for a memory leak in WL#6570 ++# ++CREATE TABLE t1 (a VARCHAR(110)); ++INSERT INTO t1 VALUES (REPEAT("a",100)); ++SELECT (t1.a,t1.a) IN (('a','c'),('a','b')) END FROM t1; ++END ++0 ++SELECT DECODE(t1.a, 'a', 'c', 'd') FROM t1; ++DECODE(t1.a, 'a', 'c', 'd') ++d ++DROP TABLE t1; ++DROP TABLE source; ++# ++# test null in expr. ++# ++SELECT decode(null, 1, 'one', null, 'is_null', 'others'); ++decode(null, 1, 'one', null, 'is_null', 'others') ++is_null ++SELECT decode(null, null, 'is_null', 2, 'two', 'others'); ++decode(null, null, 'is_null', 2, 'two', 'others') ++is_null ++SELECT decode(null, 2, 'two', 'others'); ++decode(null, 2, 'two', 'others') ++others ++SELECT decode(null, 2, 'two'); ++decode(null, 2, 'two') ++NULL +diff --git a/mysql-test/t/decode.test b/mysql-test/t/decode.test +new file mode 100644 +index 00000000000..d114a0cdd38 +--- /dev/null ++++ b/mysql-test/t/decode.test +@@ -0,0 +1,170 @@ ++# Testing of DECODE ++# ++# note: almost all test cases are migrated from CASE, even bug num. ++ ++--disable_warnings ++drop table if exists t1, t2; ++--enable_warnings ++ ++SET sql_mode = 'NO_ENGINE_SUBSTITUTION'; ++ ++ ++select DECODE("b", "a", 1, "b", 2); ++select DECODE("c", "a", 1, "b", 2); ++select DECODE("c", "a", 1, "b", 2, 3); ++select DECODE(BINARY "b", "a", 1, "B", 2, "b", "ok"); ++select DECODE("b", "a", 1, "B", 2, "b", "ok"); ++select DECODE(concat("a","b"), concat("ab",""), "a", "b", "b"); ++select DECODE(1=0, false, "true", "false"); ++select DECODE(1, 1, "one", 2, "two", "more"); ++explain select DECODE(1, 1, "one", 2, "two", "more"); ++select DECODE(2.0, 1, "one", 2.0, "two", "more"); ++select DECODE(1, 1, true, 1, false) from dual; ++select DECODE("two", "one", "1", "two", "2") | 0; ++select DECODE("two","one", 1.00, "two", 2.00) +0.0; ++select DECODE(1/0, "a", "true", "false"); ++select DECODE(1/0, "a", "true"); ++select DECODE(1/0, "a", "true") | 0; ++select DECODE(1/0, "a", "true") + 0.0; ++select DECODE(1>0, true, "TRUE", "FALSE"); ++select DECODE(1<0, false, "TRUE", "FALSE"); ++ ++# ++# Test GROUP BY on DECODE ++# ++ ++create table t1 (a int); ++insert into t1 values(1),(2),(3),(4); ++select decode(a, 1, 2, 2, 3, 0) as fcase, count(*) from t1 group by fcase; ++analyze table t1; ++explain select decode(a, 1, 2, 2, 3, 0) as fcase, count(*) from t1 group by fcase; ++select decode(a, 1, "one", 2, "two", "nothing") as fcase, count(*) from t1 group by fcase; ++drop table t1; ++ ++# ++# Test MAX(DECODE(...)) that can return null ++# ++ ++create table t1 (`row` int not null, col int not null, val varchar(255) not null); ++insert into t1 values (1,1,'orange'),(1,2,'large'),(2,1,'yellow'),(2,2,'medium'),(3,1,'green'),(3,2,'small'); ++select max(decode(col, 1, val, null)) as color from t1 group by `row`; ++drop table t1; ++ ++SET NAMES latin1; ++ ++# ++# DECODE and argument types/collations aggregation into result ++# ++CREATE TABLE t1 SELECT ++ DECODE(1, true, _latin1'a' COLLATE latin1_danish_ci, _latin1'a') AS c1, ++ DECODE(1, true, _latin1'a', _latin1'a' COLLATE latin1_danish_ci) AS c2, ++ DECODE(1, true, 'a', 1) AS c3, ++ DECODE(1, true, 1, 'a') AS c4, ++ DECODE(1, true, 'a', 1.0) AS c5, ++ DECODE(1, true, 1.0, 'a') AS c6, ++ DECODE(1, true, 1, 1.0) AS c7, ++ DECODE(1, true, 1.0, 1) AS c8, ++ DECODE(1, true, 1.0) AS c9, ++ DECODE(1, true, 0.1e1, 0.1) AS c10, ++ DECODE(1, true, 0.1e1, 1) AS c11, ++ DECODE(1, true, 0.1e1, '1') AS c12 ++; ++SHOW CREATE TABLE t1; ++DROP TABLE t1; ++ ++--error 1267 ++SELECT DECODE(1, true, _latin1'a' COLLATE latin1_danish_ci, _latin1'a' COLLATE latin1_swedish_ci); ++ ++--error 1270 ++SELECT DECODE(_latin1'a' COLLATE latin1_general_ci, ++_latin1'a' COLLATE latin1_danish_ci, 1, ++_latin1'a' COLLATE latin1_swedish_ci, 2 ++); ++ ++SELECT ++DECODE(_latin1'a' COLLATE latin1_general_ci, _latin1'A', '1', 2), ++DECODE(_latin1'a' COLLATE latin1_bin, _latin1'A', '1', 2), ++DECODE(_latin1'a', _latin1'A' COLLATE latin1_swedish_ci, '1', 2), ++DECODE(_latin1'a', _latin1'A' COLLATE latin1_bin, '1', 2); ++ ++# Test for BUG#10151 ++SELECT 'decode+union+test' ++UNION ++SELECT DECODE(LOWER('1'), LOWER('2'), 'BUG', 'nobug'); ++ ++SELECT DECODE(LOWER('1'), LOWER('2'), 'BUG', 'nobug'); ++ ++ ++# ++#test MIN(DECODE(...)) ++# ++ ++create table t1(a float, b int default 3); ++insert into t1 (a) values (2), (11), (8); ++select min(a), min(decode(1=1, true, a, NULL)), ++ min(decode(1!=1, true, NULL, a)) ++from t1 where b=3 group by b; ++drop table t1; ++ ++ ++# ++# #30782: Truncated UNSIGNED BIGINT columns ++# ++create table t1 (a int, b bigint unsigned); ++create table t2 (c int); ++insert into t1 (a, b) values (1,4572794622775114594), (2,18196094287899841997), ++ (3,11120436154190595086); ++insert into t2 (c) values (1), (2), (3); ++select t1.a, decode(t1.a, 0, 0, t1.b) d from t1 ++ join t2 on t1.a=t2.c order by d; ++select t1.a, decode(t1.a, 0, 0, t1.b) d from t1 ++ join t2 on t1.a=t2.c where b=11120436154190595086 order by d; ++drop table t1, t2; ++ ++# ++# Bug #11764313 57135: CRASH IN ITEM_FUNC_CASE::FIND_ITEM WITH CASE WHEN ++# ELSE CLAUSE ++# ++CREATE TABLE t1(a YEAR); ++SELECT 1 FROM t1 WHERE a=1 AND DECODE(1, a, 1, 1); ++DROP TABLE t1; ++ ++SET sql_mode = default; ++CREATE TABLE source(bt INTEGER, bf INTEGER, i8u BIGINT UNSIGNED, i8s BIGINT); ++INSERT INTO source VALUES ++(1,0,0,-9223372036854775808), (1,0,18446744073709551615,9223372036854775807); ++ ++CREATE TABLE target ++SELECT DECODE(bt, i8u, i8s) as u, ++ DECODE(bf, i8u, i8s) as s ++FROM source; ++ ++SHOW CREATE TABLE target; ++ ++SELECT DECODE(bt, true, i8u, i8s) AS u, ++ DECODE(bf, true, i8u, i8s) AS s ++FROM source; ++ ++SELECT * FROM target; ++ ++DROP TABLE target; ++ ++--echo # ++--echo # Test for a memory leak in WL#6570 ++--echo # ++ ++CREATE TABLE t1 (a VARCHAR(110)); ++INSERT INTO t1 VALUES (REPEAT("a",100)); ++SELECT (t1.a,t1.a) IN (('a','c'),('a','b')) END FROM t1; ++SELECT DECODE(t1.a, 'a', 'c', 'd') FROM t1; ++DROP TABLE t1; ++DROP TABLE source; ++ ++ ++--echo # ++--echo # test null in expr. ++--echo # ++SELECT decode(null, 1, 'one', null, 'is_null', 'others'); ++SELECT decode(null, null, 'is_null', 2, 'two', 'others'); ++SELECT decode(null, 2, 'two', 'others'); ++SELECT decode(null, 2, 'two'); +diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc +index fc8284df4dc..c8c88fb1aed 100644 +--- a/sql/item_cmpfunc.cc ++++ b/sql/item_cmpfunc.cc +@@ -3666,7 +3666,12 @@ Item *Item_func_case::find_item(String *) { + } + } else { + /* Compare every WHEN argument with it and return the first match */ ++ bool ora_check_null = (orafun == 1) && args[first_expr_num]->is_null(); + for (uint i = 0; i < ncases; i += 2) { ++ if (ora_check_null) { ++ if (args[i]->is_null()) return args[i + 1]; ++ continue; ++ } + if (args[i]->real_item()->type() == NULL_ITEM) continue; + cmp_type = item_cmp_type(left_result_type, args[i]->result_type()); + assert(cmp_type != ROW_RESULT); +@@ -4044,6 +4049,23 @@ uint Item_func_case::decimal_precision() const { + + void Item_func_case::print(const THD *thd, String *str, + enum_query_type query_type) const { ++ if (orafun == 1) { ++ assert(first_expr_num != -1); ++ str->append(STRING_WITH_LEN("decode(")); ++ args[first_expr_num]->print(thd, str, query_type); ++ for (uint i = 0; i < ncases; i += 2) { ++ str->append(','); ++ args[i]->print(thd, str, query_type); ++ str->append(','); ++ args[i + 1]->print(thd, str, query_type); ++ } ++ if (else_expr_num != -1) { ++ str->append(','); ++ args[else_expr_num]->print(thd, str, query_type); ++ } ++ str->append(STRING_WITH_LEN(")")); ++ return; ++ } + str->append(STRING_WITH_LEN("(case ")); + if (first_expr_num != -1) { + args[first_expr_num]->print(thd, str, query_type); +diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h +index 13ec2f38257..555423c3397 100644 +--- a/sql/item_cmpfunc.h ++++ b/sql/item_cmpfunc.h +@@ -1970,9 +1970,12 @@ class Item_func_case final : public Item_func { + cmp_item *cmp_items[5]; /* For all result types */ + cmp_item *case_item; + ++ // from oracmp function. 1:decode ++ uint orafun{0}; ++ + public: + Item_func_case(const POS &pos, mem_root_deque *list, +- Item *first_expr_arg, Item *else_expr_arg) ++ Item *first_expr_arg, Item *else_expr_arg, int ora_arg = 0) + : super(pos), + first_expr_num(-1), + else_expr_num(-1), +@@ -1991,6 +1994,7 @@ class Item_func_case final : public Item_func { + } + set_arguments(list, true); + memset(&cmp_items, 0, sizeof(cmp_items)); ++ orafun = ora_arg; + } + ~Item_func_case() override; + int get_first_expr_num() const { return first_expr_num; } +diff --git a/sql/lex.h b/sql/lex.h +index a7251ee2968..67c5967f550 100644 +--- a/sql/lex.h ++++ b/sql/lex.h +@@ -846,6 +846,7 @@ static const SYMBOL symbols[] = { + {SYM_FN("CURTIME", CURTIME)}, + {SYM_FN("DATE_ADD", DATE_ADD_INTERVAL)}, + {SYM_FN("DATE_SUB", DATE_SUB_INTERVAL)}, ++ {SYM_FN("DECODE", DECODE_SYM)}, + {SYM_FN("EXTRACT", EXTRACT_SYM)}, + {SYM_FN("GROUP_CONCAT", GROUP_CONCAT_SYM)}, + {SYM_FN("JSON_OBJECTAGG", JSON_OBJECTAGG)}, +diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy +index 35aaab16c2b..dfb7a854cbf 100644 +--- a/sql/sql_yacc.yy ++++ b/sql/sql_yacc.yy +@@ -1371,6 +1371,8 @@ void warn_about_deprecated_binary(THD *thd) + %token USER_STATS_SYM 1307 + %token ENCRYPTION_KEY_ID_SYM 1308 + ++%token DECODE_SYM 1360 ++ + /* + Tokens from Percona Server 8.0 + */ +@@ -1524,7 +1526,7 @@ void warn_about_deprecated_binary(THD *thd) + + %type + literal insert_ident temporal_literal +- simple_ident expr opt_expr opt_else ++ simple_ident expr opt_expr opt_else opt_decode_expr + set_function_specification sum_expr + in_sum_expr grouping_operation + window_func_call opt_ll_default +@@ -1569,6 +1571,7 @@ void warn_about_deprecated_binary(THD *thd) + opt_filter_table_list filter_table_list + opt_filter_string_list filter_string_list + opt_filter_db_pair_list filter_db_pair_list ++ decode_list + + %type + expr_list udf_expr_list opt_udf_expr_list opt_expr_list select_item_list +@@ -10637,6 +10640,10 @@ simple_expr: + { + $$= NEW_PTN Item_func_case(@$, $3, $2, $4 ); + } ++ | DECODE_SYM '(' expr decode_list opt_decode_expr ')' ++ { ++ $$= NEW_PTN Item_func_case(@$, $4, $3, $5, 1); ++ } + | CONVERT_SYM '(' expr ',' cast_type ')' + { + $$= create_func_cast(YYTHD, @3, $3, $5, false); +@@ -11894,6 +11901,11 @@ opt_else: + | ELSE expr { $$= $2; } + ; + ++opt_decode_expr: ++ /* empty */ { $$= NULL; } ++ |',' expr { $$= $2; } ++ ; ++ + when_list: + WHEN_SYM expr THEN_SYM expr + { +@@ -11930,6 +11942,25 @@ esc_table_reference: + table_factor { $$= $1; } + | joined_table { $$= $1; } + ; ++ ++decode_list: ++ ',' expr ',' expr ++ { ++ $$ = new (YYMEM_ROOT) mem_root_deque(YYMEM_ROOT); ++ ++ if($$ == NULL) ++ MYSQL_YYABORT; ++ $$->push_back($2); ++ $$->push_back($4); ++ } ++ | decode_list ',' expr ',' expr ++ { ++ $1->push_back($3); ++ $1->push_back($5); ++ $$=$1; ++ } ++ ; ++ + /* + Join operations are normally left-associative, as in \ No newline at end of file