Repository: pingcap/tidb (Go)

Issue:
QUOTE(null) should return 'NULL' string instead of NULL value
## Bug Report

In doc:

![image](https://user-images.githubusercontent.com/23725000/62264955-c9f46380-b454-11e9-8e62-6d67c87dbd4a.png)

quote(null) should return 'NULL' string, but tidb return NULL value instead.

1. What did you do?

```sql
select quote(null) is NULL;
```

2. What did you expect to see?

in mysql:

```sql
mysql> select quote(null) is NULL;
+---------------------+
| quote(null) is NULL |
+---------------------+
|                   0 |
+---------------------+
1 row in set (0.00 sec)
```


3. What did you see instead?

In TiDB:

```sql
mysql> select quote(null) is NULL;
+---------------------+
| quote(null) is NULL |
+---------------------+
|                   1 |
+---------------------+
1 row in set (0.00 sec)
```

4. What version of TiDB are you using (`tidb-server -V` or run `select tidb_version();` on TiDB)?

```sql
mysql> select tidb_version();
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tidb_version()                                                                                                                                                                                                                                                                                              |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Release Version: v3.0.0
Git Commit Hash: 60965b006877ca7234adaced7890d7b029ed1306
Git Branch: HEAD
UTC Build Time: 2019-06-28 12:14:07
GoVersion: go version go1.12 linux/amd64
Race Enabled: false
TiKV Min Version: 2.1.0-alpha.1-ff3dd160846b7d1aed9079c389fc188f7f5ea13e
Check Table Before Drop: false |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
```

Mysql version is 5.7




Discussion:
I will check this issue
I looked into the definition [here](https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_quote). but it's weird for mysql impl (or I can say it's wrong).
See below cases:
```
mysql> SELECT QUOTE(NULL);
+-------------+
| QUOTE(NULL) |
+-------------+
| NULL        |
+-------------+
1 row in set (0.00 sec)

mysql> SELECT NULL is NULL;
+--------------+
| NULL is NULL |
+--------------+
|            1 |
+--------------+
1 row in set (0.00 sec)

mysql> SELECT "" is NULL;
+------------+
| "" is NULL |
+------------+
|          0 |
+------------+
1 row in set (0.00 sec)

mysql> SELECT QUOTE(NULL) IS null;
+---------------------+
| QUOTE(NULL) IS null |
+---------------------+
|                   0 |
+---------------------+
1 row in set (0.00 sec)
mysql> select @@version;
+-----------+
| @@version |
+-----------+
| 5.7.13    |
+-----------+
1 row in set (0.00 sec)

```
as you can see, the NULL IS NULL = true = 1 and according to the doc, the quote(NULL) should be NULL. so the value should be 1 instead of 0.
I think this is wrong for mysql's implementation.

I will compare with latest mysql and check in the community
Aha, I got more information about this issue.
the cause is the doc is wrong. t**he quote(null) will return a string "NULL" instead of NULL is mysql.**
see my below test:
```
mysql> select length(quote(null));
+---------------------+
| length(quote(null)) |
+---------------------+
|                   4 |
+---------------------+
1 row in set (0.00 sec)

mysql> select quote(null) REGEXP 'NULL'
    -> ;
+---------------------------+
| quote(null) REGEXP 'NULL' |
+---------------------------+
|                         1 |
+---------------------------+
1 row in set (0.00 sec)

mysql> select quote(null) REGEXP 'null'
    -> ;
+---------------------------+
| quote(null) REGEXP 'null' |
+---------------------------+
|                         1 |
+---------------------------+

```
it's the upper case "NULL" instead of null.
See the case sensitive version:
```
mysql> select quote(null) REGEXP binary 'null'
    -> ;
+----------------------------------+
| quote(null) REGEXP binary 'null' |
+----------------------------------+
|                                0 |
+----------------------------------+
1 row in set (0.00 sec)

mysql> select quote(null) REGEXP binary 'NULL';
+----------------------------------+
| quote(null) REGEXP binary 'NULL' |
+----------------------------------+
|                                1 |
+----------------------------------+
1 row in set (0.00 sec)

```
@gaoxingliang    I think doc say very clearly `quote(null)` will return string 'NULL',  and mysql impl is right
Yes. u r right. I confirmed. I will try to fix this issue.



Patch:
diff --git a/expression/builtin_string.go b/expression/builtin_string.go
index 38cd2c09faffc..7288ae1afbf0e 100644
--- a/expression/builtin_string.go
+++ b/expression/builtin_string.go
@@ -2695,8 +2695,11 @@ func (b *builtinQuoteSig) Clone() builtinFunc {
 // See https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_quote
 func (b *builtinQuoteSig) evalString(row chunk.Row) (string, bool, error) {
 	str, isNull, err := b.args[0].EvalString(b.ctx, row)
-	if isNull || err != nil {
+	if err != nil {
 		return "", true, err
+	} else if isNull {
+		// If the argument is NULL, the return value is the word "NULL" without enclosing single quotation marks. see ref.
+		return "NULL", false, err
 	}
 
 	runes := []rune(str)


Test patch:
diff --git a/expression/builtin_string_test.go b/expression/builtin_string_test.go
index a9bcaf581ae15..7a47ac1b4cd7a 100644
--- a/expression/builtin_string_test.go
+++ b/expression/builtin_string_test.go
@@ -2171,7 +2171,7 @@ func (s *testEvaluatorSuite) TestQuote(c *C) {
 		{`萌萌哒(๑•ᴗ•๑)😊`, `'萌萌哒(๑•ᴗ•๑)😊'`},
 		{`㍿㌍㍑㌫`, `'㍿㌍㍑㌫'`},
 		{string([]byte{0, 26}), `'\0\Z'`},
-		{nil, nil},
+		{nil, "NULL"},
 	}
 
 	for _, t := range tbl {
diff --git a/expression/integration_test.go b/expression/integration_test.go
index ca73a617beaf8..52137a5096ac4 100644
--- a/expression/integration_test.go
+++ b/expression/integration_test.go
@@ -901,7 +901,14 @@ func (s *testIntegrationSuite) TestStringBuiltin(c *C) {
 	result = tk.MustQuery(`select quote("aaaa"), quote(""), quote("\"\""), quote("\n\n");`)
 	result.Check(testkit.Rows("'aaaa' '' '\"\"' '\n\n'"))
 	result = tk.MustQuery(`select quote(0121), quote(0000), quote("中文"), quote(NULL);`)
-	result.Check(testkit.Rows("'121' '0' '中文' <nil>"))
+	result.Check(testkit.Rows("'121' '0' '中文' NULL"))
+	tk.MustQuery(`select quote(null) is NULL;`).Check(testkit.Rows(`0`))
+	tk.MustQuery(`select quote(null) is NOT NULL;`).Check(testkit.Rows(`1`))
+	tk.MustQuery(`select length(quote(null));`).Check(testkit.Rows(`4`))
+	tk.MustQuery(`select quote(null) REGEXP binary 'null'`).Check(testkit.Rows(`0`))
+	tk.MustQuery(`select quote(null) REGEXP binary 'NULL'`).Check(testkit.Rows(`1`))
+	tk.MustQuery(`select quote(null) REGEXP 'NULL'`).Check(testkit.Rows(`1`))
+	tk.MustQuery(`select quote(null) REGEXP 'null'`).Check(testkit.Rows(`1`))
 
 	// for convert
 	result = tk.MustQuery(`select convert("123" using "binary"), convert("中文" using "binary"), convert("中文" using "utf8"), convert("中文" using "utf8mb4"), convert(cast("中文" as binary) using "utf8");`)