From d902cd116469ad6e51717b2e183c53abf15a66c9 Mon Sep 17 00:00:00 2001
From: Nikita Pettik <korablev@tarantool.org>
Date: Thu, 7 Mar 2019 18:36:57 +0300
Subject: [PATCH] sql: make LIKE accept only TEXT arguments

According to ANSI[1] specification, LIKE arguments must be of
string-like type (VARCHAR, CHAR, TEXT etc). If one of arguments is NULL,
then the result of LIKE function is NULL as well. This patch makes LIKE
follow these rules.

ANSI 2013 Part 2: Foundation; Chapter 8.5 <like predicate>

Closes #3954
---
 src/box/sql/func.c            | 18 +++++++++++---
 src/box/sql/vdbe.c            |  6 +----
 src/box/sql/vdbeInt.h         |  6 +++++
 test/sql-tap/tkt1537.test.lua |  4 ++--
 test/sql/types.result         | 45 +++++++++++++++++++++++++++++++++++
 test/sql/types.test.lua       | 15 ++++++++++++
 6 files changed, 84 insertions(+), 10 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index e84a387ac1..a750e52a1b 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -809,6 +809,9 @@ sql_strlike_ci(const char *zPattern, const char *zStr, unsigned int esc)
  *       A LIKE B
  *
  * are implemented as like(B,A).
+ *
+ * Both arguments (A and B) must be of type TEXT. If one arguments
+ * is NULL then result is NULL as well.
  */
 static void
 likeFunc(sql_context *context, int argc, sql_value **argv)
@@ -817,10 +820,19 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 	int nPat;
 	sql *db = sql_context_db_handle(context);
 	int is_like_ci = SQL_PTR_TO_INT(sql_user_data(context));
+	int rhs_type = sql_value_type(argv[0]);
+	int lhs_type = sql_value_type(argv[1]);
 
-	if (sql_value_type(argv[0]) == SQL_BLOB
-	    || sql_value_type(argv[1]) == SQL_BLOB) {
-		sql_result_int(context, 0);
+	if (lhs_type != SQL_TEXT || rhs_type != SQL_TEXT) {
+		if (lhs_type == SQL_NULL || rhs_type == SQL_NULL)
+			return;
+		char *inconsistent_type = rhs_type != SQL_TEXT ?
+					  mem_type_to_str(argv[0]) :
+					  mem_type_to_str(argv[1]);
+		diag_set(ClientError, ER_INCONSISTENT_TYPES, "TEXT",
+			 inconsistent_type);
+		context->fErrorOrAux = 1;
+		context->isError = SQL_TARANTOOL_ERROR;
 		return;
 	}
 	const char *zB = (const char *) sql_value_text(argv[0]);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index d66fdd1f32..ed7bf8870e 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -617,11 +617,7 @@ vdbe_add_new_autoinc_id(struct Vdbe *vdbe, int64_t id)
 	return 0;
 }
 
-/**
- * Simple type to str convertor. It is used to simplify
- * error reporting.
- */
-static char *
+char *
 mem_type_to_str(const struct Mem *p)
 {
 	assert(p != NULL);
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 61b7d58b28..c84f22caf9 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -262,6 +262,12 @@ enum {
 	MEM_PURE_TYPE_MASK = 0x1f
 };
 
+/**
+ * Simple type to str convertor. It is used to simplify
+ * error reporting.
+ */
+char *
+mem_type_to_str(const struct Mem *p);
 
 /* Return TRUE if Mem X contains dynamically allocated content - anything
  * that needs to be deallocated to avoid a leak.
diff --git a/test/sql-tap/tkt1537.test.lua b/test/sql-tap/tkt1537.test.lua
index 29f2bff7e9..a5a45601c4 100755
--- a/test/sql-tap/tkt1537.test.lua
+++ b/test/sql-tap/tkt1537.test.lua
@@ -185,7 +185,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "tkt1537-3.1",
     [[
-        SELECT * FROM t1 LEFT JOIN t2 ON b LIKE 'abc%' WHERE t1.id=1;
+        SELECT * FROM t1 LEFT JOIN t2 ON printf('%d', b) LIKE 'abc%' WHERE t1.id=1;
     ]], {
         -- <tkt1537-3.1>
         1, "", "", "", ""
@@ -195,7 +195,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "tkt1537-3.2",
     [[
-        SELECT * FROM t2 LEFT JOIN t1 ON a1 LIKE 'abc%' WHERE t2.id=3;
+        SELECT * FROM t2 LEFT JOIN t1 ON printf('%d', a1) LIKE 'abc%' WHERE t2.id=3;
     ]], {
         -- <tkt1537-3.2>
         3, 1, "", "", ""
diff --git a/test/sql/types.result b/test/sql/types.result
index 1220cc0dea..dd9011bf82 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -172,3 +172,48 @@ box.sql.execute("VALUES (TYPEOF(randomblob(5) || zeroblob(5)));")
 ---
 - - ['blob']
 ...
+-- gh-3954: LIKE accepts only arguments of type TEXT and NULLs.
+--
+box.sql.execute("CREATE TABLE t1 (s SCALAR PRIMARY KEY);")
+---
+...
+box.sql.execute("INSERT INTO t1 VALUES (randomblob(5));")
+---
+...
+box.sql.execute("SELECT * FROM t1 WHERE s LIKE 'blob';")
+---
+- error: 'Inconsistent types: expected TEXT got BLOB'
+...
+box.sql.execute("SELECT * FROM t1 WHERE 'blob' LIKE s;")
+---
+- error: 'Inconsistent types: expected TEXT got BLOB'
+...
+box.sql.execute("SELECT * FROM t1 WHERE 'blob' LIKE x'0000';")
+---
+- error: 'Inconsistent types: expected TEXT got BLOB'
+...
+box.sql.execute("SELECT s LIKE NULL FROM t1;")
+---
+- - [null]
+...
+box.sql.execute("DELETE FROM t1;")
+---
+...
+box.sql.execute("INSERT INTO t1 VALUES (1);")
+---
+...
+box.sql.execute("SELECT * FROM t1 WHERE s LIKE 'int';")
+---
+- error: 'Inconsistent types: expected TEXT got INTEGER'
+...
+box.sql.execute("SELECT * FROM t1 WHERE 'int' LIKE 4;")
+---
+- error: 'Inconsistent types: expected TEXT got INTEGER'
+...
+box.sql.execute("SELECT NULL LIKE s FROM t1;")
+---
+- - [null]
+...
+box.space.T1:drop()
+---
+...
diff --git a/test/sql/types.test.lua b/test/sql/types.test.lua
index 799e9aed37..bb8bf7db79 100644
--- a/test/sql/types.test.lua
+++ b/test/sql/types.test.lua
@@ -55,3 +55,18 @@ box.sql.execute("SELECT randomblob(5) || 'x';")
 -- Result of BLOBs concatenation must be BLOB.
 --
 box.sql.execute("VALUES (TYPEOF(randomblob(5) || zeroblob(5)));")
+
+-- gh-3954: LIKE accepts only arguments of type TEXT and NULLs.
+--
+box.sql.execute("CREATE TABLE t1 (s SCALAR PRIMARY KEY);")
+box.sql.execute("INSERT INTO t1 VALUES (randomblob(5));")
+box.sql.execute("SELECT * FROM t1 WHERE s LIKE 'blob';")
+box.sql.execute("SELECT * FROM t1 WHERE 'blob' LIKE s;")
+box.sql.execute("SELECT * FROM t1 WHERE 'blob' LIKE x'0000';")
+box.sql.execute("SELECT s LIKE NULL FROM t1;")
+box.sql.execute("DELETE FROM t1;")
+box.sql.execute("INSERT INTO t1 VALUES (1);")
+box.sql.execute("SELECT * FROM t1 WHERE s LIKE 'int';")
+box.sql.execute("SELECT * FROM t1 WHERE 'int' LIKE 4;")
+box.sql.execute("SELECT NULL LIKE s FROM t1;")
+box.space.T1:drop()
-- 
GitLab