Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v4 0/5] Change implicit cast for assignment
@ 2020-07-13  5:32 imeevma
  2020-07-13  5:32 ` [Tarantool-patches] [PATCH v4 1/5] sql: set field_type in mem_set_*() functions imeevma
                   ` (4 more replies)
  0 siblings, 5 replies; 11+ messages in thread
From: imeevma @ 2020-07-13  5:32 UTC (permalink / raw)
  To: korablev, tsafin, tarantool-patches

This patch-set changes implicit cast for assignment.

https://github.com/tarantool/tarantool/issues/3809
https://github.com/tarantool/tarantool/tree/imeevma/gh-3809-disallow-imlicit-cast-from-string-to-nums

@ChangeLog
 - [Breaking change] Define new rules for implicit cast for assignment.

v2:
 - Added a commit that changes the type mismatch error
   description.

v3:
 - Introduction of mem_set_double() was moved to another commit.
 - Implicit cast rules for assigning were changed.

v4:
 - Added type checking and implicit casting of arguments to
   built-in functions.
 - Removed unnecessary patches from the set.
 - Now this patch-set fixes implicit cast only for assignment.

Mergen Imeev (5):
  sql: set field_type in mem_set_*() functions
  sql: move diag setting to sql_func_by_signature()
  sql: check number of arguments in sql_func_by_signature()
  sql: change implicit cast for assignment
  sql: properly check arguments of functions

 src/box/sql/expr.c                          |   25 +-
 src/box/sql/func.c                          |  576 +++--
 src/box/sql/resolve.c                       |   23 +-
 src/box/sql/select.c                        |   31 +
 src/box/sql/sqlInt.h                        |   52 +-
 src/box/sql/vdbe.c                          |  166 +-
 src/box/sql/vdbeInt.h                       |    4 +
 src/box/sql/vdbemem.c                       |   10 +
 test/sql-tap/autoinc.test.lua               |    2 +-
 test/sql-tap/cse.test.lua                   |    8 +-
 test/sql-tap/default.test.lua               |    6 +-
 test/sql-tap/e_select1.test.lua             |   27 +-
 test/sql-tap/func.test.lua                  |   56 +-
 test/sql-tap/func2.test.lua                 |   18 +-
 test/sql-tap/func5.test.lua                 |    6 +-
 test/sql-tap/in3.test.lua                   |   14 +-
 test/sql-tap/in4.test.lua                   |   96 +-
 test/sql-tap/index1.test.lua                |   24 +-
 test/sql-tap/insert3.test.lua               |   10 +-
 test/sql-tap/intpkey.test.lua               |  133 +-
 test/sql-tap/limit.test.lua                 |    2 +-
 test/sql-tap/minmax2.test.lua               |    6 +-
 test/sql-tap/misc1.test.lua                 |   24 +-
 test/sql-tap/numcast.test.lua               |    6 +-
 test/sql-tap/orderby1.test.lua              |    2 +-
 test/sql-tap/position.test.lua              |    6 +-
 test/sql-tap/select1.test.lua               |   12 +-
 test/sql-tap/select4.test.lua               |   12 +-
 test/sql-tap/select7.test.lua               |    2 +-
 test/sql-tap/sort.test.lua                  |    8 +-
 test/sql-tap/subquery.test.lua              |   69 +-
 test/sql-tap/tkt-3998683a16.test.lua        |   26 +-
 test/sql-tap/tkt-54844eea3f.test.lua        |    8 +-
 test/sql-tap/tkt-7bbfb7d442.test.lua        |    4 +-
 test/sql-tap/tkt-9a8b09f8e6.test.lua        |   62 +-
 test/sql-tap/tkt-f973c7ac31.test.lua        |   18 +-
 test/sql-tap/tkt-fc7bd6358f.test.lua        |  111 -
 test/sql-tap/tkt1444.test.lua               |    4 +-
 test/sql-tap/tkt3493.test.lua               |    6 +-
 test/sql-tap/tkt3841.test.lua               |   12 +-
 test/sql-tap/transitive1.test.lua           |    4 +-
 test/sql-tap/triggerA.test.lua              |    4 +-
 test/sql-tap/unique.test.lua                |    8 +-
 test/sql-tap/view.test.lua                  |    2 +-
 test/sql-tap/where5.test.lua                |   10 +-
 test/sql-tap/whereB.test.lua                |  908 -------
 test/sql-tap/whereC.test.lua                |    8 +-
 test/sql/boolean.result                     |   32 +-
 test/sql/collation.result                   |    2 +-
 test/sql/gh-4159-function-argumens.result   | 2496 +++++++++++++++++++
 test/sql/gh-4159-function-argumens.test.sql |  465 ++++
 test/sql/types.result                       |  696 +++++-
 test/sql/types.test.lua                     |  131 +-
 53 files changed, 4636 insertions(+), 1817 deletions(-)
 delete mode 100755 test/sql-tap/tkt-fc7bd6358f.test.lua
 delete mode 100755 test/sql-tap/whereB.test.lua
 create mode 100644 test/sql/gh-4159-function-argumens.result
 create mode 100644 test/sql/gh-4159-function-argumens.test.sql

-- 
2.25.1

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [Tarantool-patches] [PATCH v4 1/5] sql: set field_type in mem_set_*() functions
  2020-07-13  5:32 [Tarantool-patches] [PATCH v4 0/5] Change implicit cast for assignment imeevma
@ 2020-07-13  5:32 ` imeevma
  2020-07-13 10:36   ` Nikita Pettik
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 2/5] sql: move diag setting to sql_func_by_signature() imeevma
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: imeevma @ 2020-07-13  5:32 UTC (permalink / raw)
  To: korablev, tsafin, tarantool-patches

After this patch, the mem_set _*() functions will set the mem field type
along with its MEM type flag.
---
 src/box/sql/vdbemem.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 2e0d0bc3b..8e9ebf7ab 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -829,6 +829,7 @@ mem_set_bool(struct Mem *mem, bool value)
 	sqlVdbeMemSetNull(mem);
 	mem->u.b = value;
 	mem->flags = MEM_Bool;
+	mem->field_type = FIELD_TYPE_BOOLEAN;
 }
 
 void
@@ -839,6 +840,7 @@ mem_set_i64(struct Mem *mem, int64_t value)
 	mem->u.i = value;
 	int flag = value < 0 ? MEM_Int : MEM_UInt;
 	MemSetTypeFlag(mem, flag);
+	mem->field_type = FIELD_TYPE_INTEGER;
 }
 
 void
@@ -848,6 +850,7 @@ mem_set_u64(struct Mem *mem, uint64_t value)
 		sqlVdbeMemSetNull(mem);
 	mem->u.u = value;
 	MemSetTypeFlag(mem, MEM_UInt);
+	mem->field_type = FIELD_TYPE_UNSIGNED;
 }
 
 void
@@ -863,6 +866,7 @@ mem_set_int(struct Mem *mem, int64_t value, bool is_neg)
 		mem->u.u = value;
 		MemSetTypeFlag(mem, MEM_UInt);
 	}
+	mem->field_type = FIELD_TYPE_INTEGER;
 }
 
 void
@@ -873,6 +877,7 @@ mem_set_double(struct Mem *mem, double value)
 		return;
 	mem->u.r = value;
 	MemSetTypeFlag(mem, MEM_Real);
+	mem->field_type = FIELD_TYPE_DOUBLE;
 }
 
 /*
@@ -1068,6 +1073,11 @@ sqlVdbeMemSetStr(Mem * pMem,	/* Memory cell to set to string value */
 
 	pMem->n = nByte;
 	pMem->flags = flags;
+	assert((pMem->flags & (MEM_Str | MEM_Blob)) != 0);
+	if ((pMem->flags & MEM_Str) != 0)
+		pMem->field_type = FIELD_TYPE_STRING;
+	else
+		pMem->field_type = FIELD_TYPE_VARBINARY;
 
 	if (nByte > iLimit) {
 		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
-- 
2.25.1

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [Tarantool-patches] [PATCH v4 2/5] sql: move diag setting to sql_func_by_signature()
  2020-07-13  5:32 [Tarantool-patches] [PATCH v4 0/5] Change implicit cast for assignment imeevma
  2020-07-13  5:32 ` [Tarantool-patches] [PATCH v4 1/5] sql: set field_type in mem_set_*() functions imeevma
@ 2020-07-13  5:33 ` imeevma
  2020-07-13 10:58   ` Nikita Pettik
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 3/5] sql: check number of arguments in sql_func_by_signature() imeevma
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: imeevma @ 2020-07-13  5:33 UTC (permalink / raw)
  To: korablev, tsafin, tarantool-patches

After this patch, the sql_func_by_signature() function will check the
found function and set diag if something is wrong.

Needed for #4159
---
 src/box/sql/expr.c    |  2 --
 src/box/sql/func.c    | 18 +++++++++++++++---
 src/box/sql/resolve.c | 23 ++---------------------
 src/box/sql/sqlInt.h  |  7 +++++--
 4 files changed, 22 insertions(+), 28 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index bc2182446..d0620b98c 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -3978,8 +3978,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			zId = pExpr->u.zToken;
 			struct func *func = sql_func_by_signature(zId, nFarg);
 			if (func == NULL) {
-				diag_set(ClientError, ER_NO_SUCH_FUNCTION,
-					 zId);
 				pParse->is_aborted = true;
 				break;
 			}
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 487cdafe1..4bbe4d4b7 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -2185,11 +2185,23 @@ struct func *
 sql_func_by_signature(const char *name, int argc)
 {
 	struct func *base = func_by_name(name, strlen(name));
-	if (base == NULL || !base->def->exports.sql)
+	if (base == NULL) {
+		diag_set(ClientError, ER_NO_SUCH_FUNCTION, name);
 		return NULL;
-
-	if (base->def->param_count != -1 && base->def->param_count != argc)
+	}
+	if (!base->def->exports.sql) {
+		diag_set(ClientError, ER_SQL_PARSER_GENERIC,
+			 tt_sprintf("function %s() is not available in SQL",
+				     name));
+		return NULL;
+	}
+	int param_count = base->def->param_count;
+	if (param_count != -1 && param_count != argc) {
+		const char *err = tt_sprintf("%d", param_count);
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+			 base->def->name, err, argc);
 		return NULL;
+	}
 	return base;
 }
 
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 6f625dc18..10c77c491 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -598,32 +598,13 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
 			zId = pExpr->u.zToken;
 			nId = sqlStrlen30(zId);
-			struct func *func = func_by_name(zId, nId);
+			struct func *func = sql_func_by_signature(zId, n);
 			if (func == NULL) {
-				diag_set(ClientError, ER_NO_SUCH_FUNCTION, zId);
-				pParse->is_aborted = true;
-				pNC->nErr++;
-				return WRC_Abort;
-			}
-			if (!func->def->exports.sql) {
-				diag_set(ClientError, ER_SQL_PARSER_GENERIC,
-					 tt_sprintf("function %.*s() is not "
-						    "available in SQL",
-						     nId, zId));
-				pParse->is_aborted = true;
-				pNC->nErr++;
-				return WRC_Abort;
-			}
-			if (func->def->param_count != -1 &&
-			    func->def->param_count != n) {
-				uint32_t argc = func->def->param_count;
-				const char *err = tt_sprintf("%d", argc);
-				diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
-					 func->def->name, err, n);
 				pParse->is_aborted = true;
 				pNC->nErr++;
 				return WRC_Abort;
 			}
+			assert(func->def->exports.sql);
 			bool is_agg = func->def->aggregate ==
 				      FUNC_AGGREGATE_GROUP;
 			assert(!is_agg || func->def->language ==
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 37283e506..58a65acc1 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -4441,8 +4441,11 @@ sql_func_flag_is_set(struct func *func, uint16_t flag)
  * export field set true and have exactly the same signature
  * are returned.
  *
- * Returns not NULL function pointer when a valid and exported
- * to SQL engine function is found and NULL otherwise.
+ * @param name Name of the function to find.
+ * @param argc Number of arguments of the function.
+ *
+ * @retval not NULL function pointer when a function is found.
+ * @retval NULL on error and sets a diag.
  */
 struct func *
 sql_func_by_signature(const char *name, int argc);
-- 
2.25.1

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [Tarantool-patches] [PATCH v4 3/5] sql: check number of arguments in sql_func_by_signature()
  2020-07-13  5:32 [Tarantool-patches] [PATCH v4 0/5] Change implicit cast for assignment imeevma
  2020-07-13  5:32 ` [Tarantool-patches] [PATCH v4 1/5] sql: set field_type in mem_set_*() functions imeevma
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 2/5] sql: move diag setting to sql_func_by_signature() imeevma
@ 2020-07-13  5:33 ` imeevma
  2020-07-13 12:21   ` Nikita Pettik
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 4/5] sql: change implicit cast for assignment imeevma
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 5/5] sql: properly check arguments of functions imeevma
  4 siblings, 1 reply; 11+ messages in thread
From: imeevma @ 2020-07-13  5:33 UTC (permalink / raw)
  To: korablev, tsafin, tarantool-patches

After this patch, the number of function arguments will always be
checked in the sql_func_by_signature() function. This was not the
case for some of the built-in functions.

Part of #4159
---
 src/box/sql/expr.c                          |  18 +-
 src/box/sql/func.c                          | 208 +++--
 src/box/sql/sqlInt.h                        |  13 +
 test/sql-tap/func.test.lua                  |   8 +-
 test/sql-tap/func2.test.lua                 |  18 +-
 test/sql-tap/func5.test.lua                 |   6 +-
 test/sql-tap/select1.test.lua               |   4 +-
 test/sql/collation.result                   |   2 +-
 test/sql/gh-4159-function-argumens.result   | 821 ++++++++++++++++++++
 test/sql/gh-4159-function-argumens.test.sql | 193 +++++
 10 files changed, 1212 insertions(+), 79 deletions(-)
 create mode 100644 test/sql/gh-4159-function-argumens.result
 create mode 100644 test/sql/gh-4159-function-argumens.test.sql

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index d0620b98c..7aee240a3 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -3987,14 +3987,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 */
 			if (sql_func_flag_is_set(func, SQL_FUNC_COALESCE)) {
 				int endCoalesce = sqlVdbeMakeLabel(v);
-				if (nFarg < 2) {
-					diag_set(ClientError,
-						 ER_FUNC_WRONG_ARG_COUNT,
-						 func->def->name,
-						 "at least two", nFarg);
-					pParse->is_aborted = true;
-					break;
-				}
+				assert(nFarg >= 2);
 				sqlExprCode(pParse, pFarg->a[0].pExpr,
 						target);
 				for (i = 1; i < nFarg; i++) {
@@ -4017,14 +4010,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 * of the first argument.
 			 */
 			if (sql_func_flag_is_set(func, SQL_FUNC_UNLIKELY)) {
-				if (nFarg < 1) {
-					diag_set(ClientError,
-						 ER_FUNC_WRONG_ARG_COUNT,
-						 func->def->name,
-						 "at least one", nFarg);
-					pParse->is_aborted = true;
-					break;
-				}
+				assert(nFarg == 1 || nFarg == 2);
 				return sqlExprCodeTarget(pParse,
 							     pFarg->a[0].pExpr,
 							     target);
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 4bbe4d4b7..9d4c26081 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -383,17 +383,12 @@ error:
 static void
 minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 {
+	assert(argc > 1);
 	int i;
 	int iBest;
 	struct coll *pColl;
 	struct func *func = context->func;
 	int mask = sql_func_flag_is_set(func, SQL_FUNC_MAX) ? -1 : 0;
-	if (argc < 2) {
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
-		mask ? "GREATEST" : "LEAST", "at least two", argc);
-		context->is_aborted = true;
-		return;
-	}
 	pColl = sqlGetFuncCollSeq(context);
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
@@ -722,6 +717,7 @@ printfFunc(sql_context * context, int argc, sql_value ** argv)
 static void
 substrFunc(sql_context * context, int argc, sql_value ** argv)
 {
+	assert(argc == 2 || argc == 3);
 	const unsigned char *z;
 	const unsigned char *z2;
 	int len;
@@ -729,12 +725,6 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	i64 p1, p2;
 	int negP2 = 0;
 
-	if (argc != 2 && argc != 3) {
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SUBSTR",
-			 "1 or 2", argc);
-		context->is_aborted = true;
-		return;
-	}
 	if (sql_value_is_null(argv[1])
 	    || (argc == 3 && sql_value_is_null(argv[2]))
 	    ) {
@@ -828,14 +818,9 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 static void
 roundFunc(sql_context * context, int argc, sql_value ** argv)
 {
+	assert(argc == 1 || argc == 2);
 	int n = 0;
 	double r;
-	if (argc != 1 && argc != 2) {
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROUND",
-			 "1 or 2", argc);
-		context->is_aborted = true;
-		return;
-	}
 	if (argc == 2) {
 		if (sql_value_is_null(argv[1]))
 			return;
@@ -1226,14 +1211,9 @@ sql_utf8_pattern_compare(const char *pattern,
 static void
 likeFunc(sql_context *context, int argc, sql_value **argv)
 {
+	assert(argc == 2 || argc == 3);
 	u32 escape = SQL_END_OF_STRING;
 	int nPat;
-	if (argc != 2 && argc != 3) {
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
-			 "LIKE", "2 or 3", argc);
-		context->is_aborted = true;
-		return;
-	}
 	sql *db = sql_context_db_handle(context);
 	int rhs_type = sql_value_type(argv[0]);
 	int lhs_type = sql_value_type(argv[1]);
@@ -1838,6 +1818,7 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
 static void
 trim_func(struct sql_context *context, int argc, sql_value **argv)
 {
+	assert(argc == 1 || argc == 2 || argc == 3);
 	switch (argc) {
 	case 1:
 		trim_func_one_arg(context, argv[0]);
@@ -1849,9 +1830,7 @@ trim_func(struct sql_context *context, int argc, sql_value **argv)
 		trim_func_three_args(context, argv[0], argv[1], argv[2]);
 		break;
 	default:
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TRIM",
-			 "1 or 2 or 3", argc);
-		context->is_aborted = true;
+		unreachable();
 	}
 }
 
@@ -2030,13 +2009,8 @@ struct CountCtx {
 static void
 countStep(sql_context * context, int argc, sql_value ** argv)
 {
+	assert(argc == 0 || argc == 1);
 	CountCtx *p;
-	if (argc != 0 && argc != 1) {
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
-			 "COUNT", "0 or 1", argc);
-		context->is_aborted = true;
-		return;
-	}
 	p = sql_aggregate_context(context, sizeof(*p));
 	if ((argc == 0 || ! sql_value_is_null(argv[0])) && p) {
 		p->n++;
@@ -2111,16 +2085,11 @@ minMaxFinalize(sql_context * context)
 static void
 groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 {
+	assert(argc == 1 || argc == 2);
 	const char *zVal;
 	StrAccum *pAccum;
 	const char *zSep;
 	int nVal, nSep;
-	if (argc != 1 && argc != 2) {
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
-			 "GROUP_CONCAT", "1 or 2", argc);
-		context->is_aborted = true;
-		return;
-	}
 	if (sql_value_is_null(argv[0]))
 		return;
 	pAccum =
@@ -2195,11 +2164,28 @@ sql_func_by_signature(const char *name, int argc)
 				     name));
 		return NULL;
 	}
-	int param_count = base->def->param_count;
-	if (param_count != -1 && param_count != argc) {
-		const char *err = tt_sprintf("%d", param_count);
-		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
-			 base->def->name, err, argc);
+	if (base->def->language != FUNC_LANGUAGE_SQL_BUILTIN) {
+		int param_count = base->def->param_count;
+		if (param_count != -1 && param_count != argc) {
+			const char *err = tt_sprintf("%d", param_count);
+			diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+				 base->def->name, err, argc);
+			return NULL;
+		}
+		return base;
+	}
+	struct func_sql_builtin *func = (struct func_sql_builtin *)base;
+	uint32_t arg_c = (uint32_t)argc;
+	if (func->args.min_count > arg_c || func->args.max_count < arg_c) {
+		const char *err;
+		uint32_t min = func->args.min_count;
+		uint32_t max = func->args.max_count;
+		if (min != max)
+			err = tt_sprintf("from %d to %d", min, max);
+		else
+			err = tt_sprintf("%d", min);
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, base->def->name,
+			 err, argc);
 		return NULL;
 	}
 	return base;
@@ -2242,12 +2228,16 @@ static struct {
 	/** Members below are related to struct func_def. */
 	bool is_deterministic;
 	int param_count;
+	uint32_t min_count;
+	uint32_t max_count;
 	enum field_type returns;
 	enum func_aggregate aggregate;
 	bool export_to_sql;
 } sql_builtins[] = {
 	{.name = "ABS",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_NUMBER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2258,6 +2248,8 @@ static struct {
 	}, {
 	 .name = "AVG",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_NUMBER,
 	 .is_deterministic = false,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
@@ -2270,6 +2262,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2280,6 +2274,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2288,6 +2284,8 @@ static struct {
 	}, {
 	 .name = "CHAR",
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = SQL_MAX_FUNCTION_ARG,
 	 .returns = FIELD_TYPE_STRING,
 	 .is_deterministic = true,
 	 .aggregate = FUNC_AGGREGATE_NONE,
@@ -2298,6 +2296,8 @@ static struct {
 	 }, {
 	 .name = "CHARACTER_LENGTH",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2308,6 +2308,8 @@ static struct {
 	}, {
 	 .name = "CHAR_LENGTH",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2318,6 +2320,8 @@ static struct {
 	}, {
 	 .name = "COALESCE",
 	 .param_count = -1,
+	 .min_count = 2,
+	 .max_count = SQL_MAX_FUNCTION_ARG,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2328,6 +2332,8 @@ static struct {
 	}, {
 	 .name = "COUNT",
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2340,6 +2346,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2350,6 +2358,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2360,6 +2370,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2370,6 +2382,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2380,6 +2394,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2390,6 +2406,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2400,6 +2418,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2410,6 +2430,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2420,6 +2442,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2430,6 +2454,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2440,6 +2466,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2448,6 +2476,8 @@ static struct {
 	}, {
 	 .name = "GREATEST",
 	 .param_count = -1,
+	 .min_count = 2,
+	 .max_count = SQL_MAX_FUNCTION_ARG,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2458,6 +2488,8 @@ static struct {
 	}, {
 	 .name = "GROUP_CONCAT",
 	 .param_count = -1,
+	 .min_count = 1,
+	 .max_count = 2,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2468,6 +2500,8 @@ static struct {
 	}, {
 	 .name = "HEX",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2478,6 +2512,8 @@ static struct {
 	}, {
 	 .name = "IFNULL",
 	 .param_count = 2,
+	 .min_count = 2,
+	 .max_count = 2,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2490,6 +2526,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2498,6 +2536,8 @@ static struct {
 	}, {
 	 .name = "LEAST",
 	 .param_count = -1,
+	 .min_count = 2,
+	 .max_count = SQL_MAX_FUNCTION_ARG,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2508,6 +2548,8 @@ static struct {
 	}, {
 	 .name = "LENGTH",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2520,6 +2562,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2528,6 +2572,8 @@ static struct {
 	}, {
 	 .name = "LIKE",
 	 .param_count = -1,
+	 .min_count = 2,
+	 .max_count = 3,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2538,6 +2584,8 @@ static struct {
 	}, {
 	 .name = "LIKELIHOOD",
 	 .param_count = 2,
+	 .min_count = 2,
+	 .max_count = 2,
 	 .returns = FIELD_TYPE_BOOLEAN,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2548,6 +2596,8 @@ static struct {
 	}, {
 	 .name = "LIKELY",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_BOOLEAN,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2560,6 +2610,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2568,6 +2620,8 @@ static struct {
 	}, {
 	 .name = "LOWER",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2578,6 +2632,8 @@ static struct {
 	}, {
 	 .name = "MAX",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2588,6 +2644,8 @@ static struct {
 	}, {
 	 .name = "MIN",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2600,6 +2658,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2608,6 +2668,8 @@ static struct {
 	}, {
 	 .name = "NULLIF",
 	 .param_count = 2,
+	 .min_count = 2,
+	 .max_count = 2,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2620,6 +2682,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2628,6 +2692,8 @@ static struct {
 	}, {
 	 .name = "POSITION",
 	 .param_count = 2,
+	 .min_count = 2,
+	 .max_count = 2,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2640,6 +2706,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2648,6 +2716,8 @@ static struct {
 	}, {
 	 .name = "PRINTF",
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = SQL_MAX_FUNCTION_ARG,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2658,6 +2728,8 @@ static struct {
 	}, {
 	 .name = "QUOTE",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2668,6 +2740,8 @@ static struct {
 	}, {
 	 .name = "RANDOM",
 	 .param_count = 0,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2678,6 +2752,8 @@ static struct {
 	}, {
 	 .name = "RANDOMBLOB",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_VARBINARY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2688,6 +2764,8 @@ static struct {
 	}, {
 	 .name = "REPLACE",
 	 .param_count = 3,
+	 .min_count = 3,
+	 .max_count = 3,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2698,6 +2776,8 @@ static struct {
 	}, {
 	 .name = "ROUND",
 	 .param_count = -1,
+	 .min_count = 1,
+	 .max_count = 2,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2708,6 +2788,8 @@ static struct {
 	}, {
 	 .name = "ROW_COUNT",
 	 .param_count = 0,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2720,6 +2802,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2728,6 +2812,8 @@ static struct {
 	}, {
 	 .name = "SOUNDEX",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2740,6 +2826,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2750,6 +2838,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2758,6 +2848,8 @@ static struct {
 	}, {
 	 .name = "SUBSTR",
 	 .param_count = -1,
+	 .min_count = 2,
+	 .max_count = 3,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2768,6 +2860,8 @@ static struct {
 	}, {
 	 .name = "SUM",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_NUMBER,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2780,6 +2874,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2788,6 +2884,8 @@ static struct {
 	}, {
 	 .name = "TOTAL",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_NUMBER,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2798,6 +2896,8 @@ static struct {
 	}, {
 	 .name = "TRIM",
 	 .param_count = -1,
+	 .min_count = 1,
+	 .max_count = 3,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2808,6 +2908,8 @@ static struct {
 	}, {
 	 .name = "TYPEOF",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2818,6 +2920,8 @@ static struct {
 	}, {
 	 .name = "UNICODE",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2828,6 +2932,8 @@ static struct {
 	}, {
 	 .name = "UNLIKELY",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_BOOLEAN,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2838,6 +2944,8 @@ static struct {
 	}, {
 	 .name = "UPPER",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2848,6 +2956,8 @@ static struct {
 	}, {
 	 .name = "VERSION",
 	 .param_count = 0,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2858,6 +2968,8 @@ static struct {
 	}, {
 	 .name = "ZEROBLOB",
 	 .param_count = 1,
+	 .min_count = 1,
+	 .max_count = 1,
 	 .returns = FIELD_TYPE_VARBINARY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2870,6 +2982,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2880,6 +2994,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2890,6 +3006,8 @@ static struct {
 	 .call = sql_builtin_stub,
 	 .export_to_sql = false,
 	 .param_count = -1,
+	 .min_count = 0,
+	 .max_count = 0,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2941,6 +3059,8 @@ func_sql_builtin_new(struct func_def *def)
 	func->flags = sql_builtins[idx].flags;
 	func->call = sql_builtins[idx].call;
 	func->finalize = sql_builtins[idx].finalize;
+	func->args.min_count = sql_builtins[idx].min_count;
+	func->args.max_count = sql_builtins[idx].max_count;
 	def->param_count = sql_builtins[idx].param_count;
 	def->is_deterministic = sql_builtins[idx].is_deterministic;
 	def->returns = sql_builtins[idx].returns;
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 58a65acc1..6af9d7473 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -4397,11 +4397,24 @@ Expr *sqlExprForVectorField(Parse *, Expr *, int);
  */
 extern int sqlSubProgramsRemaining;
 
+/**
+ * A structure that contains additional information about
+ * arguments to built-in SQL functions.
+ */
+struct sql_builtin_func_args {
+	/** Min number of arguments. */
+	uint32_t min_count;
+	/** Max number of arguments. */
+	uint32_t max_count;
+};
+
 struct func_sql_builtin {
 	/** Function object base class. */
 	struct func base;
 	/** A bitmask of SQL flags. */
 	uint16_t flags;
+	/** Information about arguments to built-in functions. */
+	struct sql_builtin_func_args args;
 	/**
 	 * A VDBE-memory-compatible call method.
 	 * SQL built-ins don't use func base class "call"
diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
index 3c088920f..1d3ef9e2a 100755
--- a/test/sql-tap/func.test.lua
+++ b/test/sql-tap/func.test.lua
@@ -428,7 +428,7 @@ test:do_catchsql_test(
         SELECT round(a,b,c) FROM t1
     ]], {
         -- <func-4.5>
-        1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 3"
+        1, "Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 3"
         -- </func-4.5>
     })
 
@@ -488,7 +488,7 @@ test:do_catchsql_test(
         SELECT round() FROM t1 ORDER BY a
     ]], {
         -- <func-4.11>
-        1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 0"
+        1, "Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 0"
         -- </func-4.11>
     })
 
@@ -2540,7 +2540,7 @@ test:do_catchsql_test(
         SELECT coalesce()
     ]], {
         -- <func-27.1>
-        1, "Wrong number of arguments is passed to COALESCE(): expected at least two, got 0"
+        1, "Wrong number of arguments is passed to COALESCE(): expected from 2 to 127, got 0"
         -- </func-27.1>
     })
 
@@ -2550,7 +2550,7 @@ test:do_catchsql_test(
         SELECT coalesce(1)
     ]], {
         -- <func-27.2>
-        1, "Wrong number of arguments is passed to COALESCE(): expected at least two, got 1"
+        1, "Wrong number of arguments is passed to COALESCE(): expected from 2 to 127, got 1"
         -- </func-27.2>
     })
 
diff --git a/test/sql-tap/func2.test.lua b/test/sql-tap/func2.test.lua
index c9bd3665f..4bfee7150 100755
--- a/test/sql-tap/func2.test.lua
+++ b/test/sql-tap/func2.test.lua
@@ -50,7 +50,7 @@ test:do_catchsql_test(
         SELECT SUBSTR()
     ]], {
         -- <func2-1.2.1>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 0"
         -- </func2-1.2.1>
     })
 
@@ -60,7 +60,7 @@ test:do_catchsql_test(
         SELECT SUBSTR('Supercalifragilisticexpialidocious')
     ]], {
         -- <func2-1.2.2>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 1"
         -- </func2-1.2.2>
     })
 
@@ -70,7 +70,7 @@ test:do_catchsql_test(
         SELECT SUBSTR('Supercalifragilisticexpialidocious', 1,1,1)
     ]], {
         -- <func2-1.2.3>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 4"
         -- </func2-1.2.3>
     })
 
@@ -673,7 +673,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR()
         ]], {
             -- <func2-2.1.2>
-            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 0"
             -- </func2-2.1.2>
         })
 
@@ -683,7 +683,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR('hiሴho')
         ]], {
             -- <func2-2.1.3>
-            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 1"
             -- </func2-2.1.3>
         })
 
@@ -693,7 +693,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR('hiሴho', 1,1,1)
         ]], {
             -- <func2-2.1.4>
-            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 4"
             -- </func2-2.1.4>
         })
 
@@ -1038,7 +1038,7 @@ test:do_catchsql_test(
         SELECT SUBSTR()
     ]], {
         -- <func2-3.1.2>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 0"
         -- </func2-3.1.2>
     })
 
@@ -1048,7 +1048,7 @@ test:do_catchsql_test(
         SELECT SUBSTR(x'1234')
     ]], {
         -- <func2-3.1.3>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 1"
         -- </func2-3.1.3>
     })
 
@@ -1058,7 +1058,7 @@ test:do_catchsql_test(
         SELECT SUBSTR(x'1234', 1,1,1)
     ]], {
         -- <func2-3.1.4>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 4"
         -- </func2-3.1.4>
     })
 
diff --git a/test/sql-tap/func5.test.lua b/test/sql-tap/func5.test.lua
index 8329e1735..08c40fd02 100755
--- a/test/sql-tap/func5.test.lua
+++ b/test/sql-tap/func5.test.lua
@@ -276,19 +276,19 @@ test:do_catchsql_test(
     "func-5-5.1",
     [[
         SELECT LEAST(false);
-    ]], { 1, "Wrong number of arguments is passed to LEAST(): expected at least two, got 1" } )
+    ]], { 1, "Wrong number of arguments is passed to LEAST(): expected from 2 to 127, got 1" } )
 
 test:do_catchsql_test(
     "func-5-5.2",
     [[
         SELECT GREATEST('abc');
-    ]], { 1, "Wrong number of arguments is passed to GREATEST(): expected at least two, got 1" } )
+    ]], { 1, "Wrong number of arguments is passed to GREATEST(): expected from 2 to 127, got 1" } )
 
 test:do_catchsql_test(
     "func-5-5.3",
     [[
         SELECT LEAST();
-    ]], { 1, "Wrong number of arguments is passed to LEAST(): expected at least two, got 0" } )
+    ]], { 1, "Wrong number of arguments is passed to LEAST(): expected from 2 to 127, got 0" } )
 
 box.func.COUNTER1:drop()
 box.func.COUNTER2:drop()
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index fbebfab37..6248abb65 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -250,7 +250,7 @@ test:do_catchsql_test(
         SELECT count(f1,f2) FROM test1
     ]], {
         -- <select1-2.1>
-        1, "Wrong number of arguments is passed to COUNT(): expected 0 or 1, got 2"
+        1, "Wrong number of arguments is passed to COUNT(): expected from 0 to 1, got 2"
         -- </select1-2.1>
     })
 
@@ -697,7 +697,7 @@ test:do_catchsql_test(
         SELECT f1 FROM test1 WHERE count(f1,f2)!=11
     ]], {
         -- <select1-3.9>
-        1, "misuse of aggregate function COUNT()"
+        1, "Wrong number of arguments is passed to COUNT(): expected from 0 to 1, got 2"
         -- </select1-3.9>
     })
 
diff --git a/test/sql/collation.result b/test/sql/collation.result
index 4e4c27ef0..9c22c7191 100644
--- a/test/sql/collation.result
+++ b/test/sql/collation.result
@@ -298,7 +298,7 @@ box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = c COLLATE \"unicode\";
 box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = substr();")
 ---
 - null
-- 'Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0'
+- 'Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 0'
 ...
 -- Compound queries perform implicit comparisons between values.
 -- Hence, rules for collations compatibilities are the same.
diff --git a/test/sql/gh-4159-function-argumens.result b/test/sql/gh-4159-function-argumens.result
new file mode 100644
index 000000000..48bd550a4
--- /dev/null
+++ b/test/sql/gh-4159-function-argumens.result
@@ -0,0 +1,821 @@
+-- test-run result file version 2
+-- Function abs().
+SELECT abs();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to ABS(): expected 1, got 0'
+ | ...
+SELECT abs(1);
+ | ---
+ | - metadata:
+ |   - name: abs(1)
+ |     type: number
+ |   rows:
+ |   - [1]
+ | ...
+SELECT abs(1, 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to ABS(): expected 1, got 2'
+ | ...
+
+-- Function char().
+SELECT char();
+ | ---
+ | - metadata:
+ |   - name: char()
+ |     type: string
+ |   rows:
+ |   - ['']
+ | ...
+SELECT char(1);
+ | ---
+ | - metadata:
+ |   - name: char(1)
+ |     type: string
+ |   rows:
+ |   - ["\x01"]
+ | ...
+SELECT char(1, 2);
+ | ---
+ | - metadata:
+ |   - name: char(1, 2)
+ |     type: string
+ |   rows:
+ |   - ["\x01\x02"]
+ | ...
+
+-- Function character_length().
+SELECT character_length();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to CHARACTER_LENGTH(): expected 1, got 0'
+ | ...
+SELECT character_length('1');
+ | ---
+ | - metadata:
+ |   - name: character_length('1')
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT character_length('1', '2');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to CHARACTER_LENGTH(): expected 1, got 2'
+ | ...
+
+-- Function char_length().
+SELECT char_length();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to CHAR_LENGTH(): expected 1, got 0'
+ | ...
+SELECT char_length('1');
+ | ---
+ | - metadata:
+ |   - name: char_length('1')
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT char_length('1', '2');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to CHAR_LENGTH(): expected 1, got 2'
+ | ...
+
+-- Function coalesce().
+SELECT coalesce();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to COALESCE(): expected from 2 to 127, got
+ |   0'
+ | ...
+SELECT coalesce('1');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to COALESCE(): expected from 2 to 127, got
+ |   1'
+ | ...
+SELECT coalesce('1', '2');
+ | ---
+ | - metadata:
+ |   - name: coalesce('1', '2')
+ |     type: scalar
+ |   rows:
+ |   - ['1']
+ | ...
+SELECT coalesce('1', '2', '3');
+ | ---
+ | - metadata:
+ |   - name: coalesce('1', '2', '3')
+ |     type: scalar
+ |   rows:
+ |   - ['1']
+ | ...
+
+-- Function greatest().
+SELECT greatest();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to GREATEST(): expected from 2 to 127, got
+ |   0'
+ | ...
+SELECT greatest('1');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to GREATEST(): expected from 2 to 127, got
+ |   1'
+ | ...
+SELECT greatest('1', '2');
+ | ---
+ | - metadata:
+ |   - name: greatest('1', '2')
+ |     type: scalar
+ |   rows:
+ |   - ['2']
+ | ...
+
+-- Function hex().
+SELECT hex();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to HEX(): expected 1, got 0'
+ | ...
+SELECT hex(X'33');
+ | ---
+ | - metadata:
+ |   - name: hex(X'33')
+ |     type: string
+ |   rows:
+ |   - ['33']
+ | ...
+SELECT hex(X'33', X'33');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to HEX(): expected 1, got 2'
+ | ...
+
+-- Function ifnull
+SELECT ifnull();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to IFNULL(): expected 2, got 0'
+ | ...
+SELECT ifnull(1);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to IFNULL(): expected 2, got 1'
+ | ...
+SELECT ifnull(1, 2);
+ | ---
+ | - metadata:
+ |   - name: ifnull(1, 2)
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT ifnull(1, 2, 3);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to IFNULL(): expected 2, got 3'
+ | ...
+
+-- Function least().
+SELECT least();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LEAST(): expected from 2 to 127, got 0'
+ | ...
+SELECT least('1');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LEAST(): expected from 2 to 127, got 1'
+ | ...
+SELECT least('1', '2');
+ | ---
+ | - metadata:
+ |   - name: least('1', '2')
+ |     type: scalar
+ |   rows:
+ |   - ['1']
+ | ...
+
+-- Function length().
+SELECT length();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LENGTH(): expected 1, got 0'
+ | ...
+SELECT length('1');
+ | ---
+ | - metadata:
+ |   - name: length('1')
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT length('1', '2');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LENGTH(): expected 1, got 2'
+ | ...
+
+-- Function likelihood
+SELECT likelihood();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LIKELIHOOD(): expected 2, got 0'
+ | ...
+SELECT likelihood(1);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LIKELIHOOD(): expected 2, got 1'
+ | ...
+SELECT likelihood(1, 0.5);
+ | ---
+ | - metadata:
+ |   - name: likelihood(1, 0.5)
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT likelihood(1, 0.5, 3);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LIKELIHOOD(): expected 2, got 3'
+ | ...
+
+-- Function likely
+SELECT likely();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LIKELY(): expected 1, got 0'
+ | ...
+SELECT likely(1);
+ | ---
+ | - metadata:
+ |   - name: likely(1)
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT likely(1, 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LIKELY(): expected 1, got 2'
+ | ...
+
+-- Function lower
+SELECT lower();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LOWER(): expected 1, got 0'
+ | ...
+SELECT lower('a');
+ | ---
+ | - metadata:
+ |   - name: lower('a')
+ |     type: string
+ |   rows:
+ |   - ['a']
+ | ...
+SELECT lower('a', 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LOWER(): expected 1, got 2'
+ | ...
+
+-- Function nullif
+SELECT nullif();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to NULLIF(): expected 2, got 0'
+ | ...
+SELECT nullif(1);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to NULLIF(): expected 2, got 1'
+ | ...
+SELECT nullif(1, 2);
+ | ---
+ | - metadata:
+ |   - name: nullif(1, 2)
+ |     type: scalar
+ |   rows:
+ |   - [1]
+ | ...
+SELECT nullif(1, 2, 3);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to NULLIF(): expected 2, got 3'
+ | ...
+
+-- Function position
+SELECT position();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to POSITION(): expected 2, got 0'
+ | ...
+SELECT position('12345');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to POSITION(): expected 2, got 1'
+ | ...
+SELECT position('12345', '2');
+ | ---
+ | - metadata:
+ |   - name: position('12345', '2')
+ |     type: integer
+ |   rows:
+ |   - [0]
+ | ...
+SELECT position('12345', '2', 3);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to POSITION(): expected 2, got 3'
+ | ...
+
+-- Function printf
+SELECT printf();
+ | ---
+ | - metadata:
+ |   - name: printf()
+ |     type: string
+ |   rows:
+ |   - [null]
+ | ...
+SELECT printf('1');
+ | ---
+ | - metadata:
+ |   - name: printf('1')
+ |     type: string
+ |   rows:
+ |   - ['1']
+ | ...
+SELECT printf('1', 2);
+ | ---
+ | - metadata:
+ |   - name: printf('1', 2)
+ |     type: string
+ |   rows:
+ |   - ['1']
+ | ...
+SELECT printf('1', 2, 3);
+ | ---
+ | - metadata:
+ |   - name: printf('1', 2, 3)
+ |     type: string
+ |   rows:
+ |   - ['1']
+ | ...
+
+-- Function quote
+SELECT quote();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to QUOTE(): expected 1, got 0'
+ | ...
+SELECT quote('1');
+ | ---
+ | - metadata:
+ |   - name: quote('1')
+ |     type: string
+ |   rows:
+ |   - ['''1''']
+ | ...
+SELECT quote('1', 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to QUOTE(): expected 1, got 2'
+ | ...
+
+-- Function random
+SELECT typeof(random());
+ | ---
+ | - metadata:
+ |   - name: typeof(random())
+ |     type: string
+ |   rows:
+ |   - ['integer']
+ | ...
+SELECT typeof(random(1));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to RANDOM(): expected 0, got 1'
+ | ...
+
+-- Function randomblob
+SELECT typeof(randomblob());
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to RANDOMBLOB(): expected 1, got 0'
+ | ...
+SELECT typeof(randomblob(1));
+ | ---
+ | - metadata:
+ |   - name: typeof(randomblob(1))
+ |     type: string
+ |   rows:
+ |   - ['varbinary']
+ | ...
+SELECT typeof(randomblob(1, 2));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to RANDOMBLOB(): expected 1, got 2'
+ | ...
+
+-- Function replace
+SELECT replace();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 0'
+ | ...
+SELECT replace('12345');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 1'
+ | ...
+SELECT replace('12345', '2');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 2'
+ | ...
+SELECT replace('12345', '2', '3');
+ | ---
+ | - metadata:
+ |   - name: replace('12345', '2', '3')
+ |     type: string
+ |   rows:
+ |   - ['13345']
+ | ...
+SELECT replace('12345', '2', '3', 4);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 4'
+ | ...
+
+-- Function round
+SELECT round();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 0'
+ | ...
+SELECT round(1.1245);
+ | ---
+ | - metadata:
+ |   - name: round(1.1245)
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT round(1.1245, 2);
+ | ---
+ | - metadata:
+ |   - name: round(1.1245, 2)
+ |     type: integer
+ |   rows:
+ |   - [1.12]
+ | ...
+SELECT round(1.1245, 2, 3);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 3'
+ | ...
+
+-- Function row_count
+SELECT row_count();
+ | ---
+ | - metadata:
+ |   - name: row_count()
+ |     type: integer
+ |   rows:
+ |   - [0]
+ | ...
+SELECT row_count(1);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to ROW_COUNT(): expected 0, got 1'
+ | ...
+
+-- Function soundex
+SELECT soundex();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SOUNDEX(): expected 1, got 0'
+ | ...
+SELECT soundex(1);
+ | ---
+ | - metadata:
+ |   - name: soundex(1)
+ |     type: string
+ |   rows:
+ |   - ['?000']
+ | ...
+SELECT soundex(1, 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SOUNDEX(): expected 1, got 2'
+ | ...
+
+-- Function substr
+SELECT substr();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 0'
+ | ...
+SELECT substr('12345');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 1'
+ | ...
+SELECT substr('12345', 2);
+ | ---
+ | - metadata:
+ |   - name: substr('12345', 2)
+ |     type: string
+ |   rows:
+ |   - ['2345']
+ | ...
+SELECT substr('12345', 2, 3);
+ | ---
+ | - metadata:
+ |   - name: substr('12345', 2, 3)
+ |     type: string
+ |   rows:
+ |   - ['234']
+ | ...
+SELECT substr('12345', 2, 3, 4);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 4'
+ | ...
+
+-- Function typeof
+SELECT typeof();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to TYPEOF(): expected 1, got 0'
+ | ...
+SELECT typeof(1);
+ | ---
+ | - metadata:
+ |   - name: typeof(1)
+ |     type: string
+ |   rows:
+ |   - ['integer']
+ | ...
+SELECT typeof(1, 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to TYPEOF(): expected 1, got 2'
+ | ...
+
+-- Function unicode
+SELECT unicode();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to UNICODE(): expected 1, got 0'
+ | ...
+SELECT unicode('1');
+ | ---
+ | - metadata:
+ |   - name: unicode('1')
+ |     type: string
+ |   rows:
+ |   - [49]
+ | ...
+SELECT unicode('1', '2');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to UNICODE(): expected 1, got 2'
+ | ...
+
+-- Function unlikely
+SELECT unlikely();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to UNLIKELY(): expected 1, got 0'
+ | ...
+SELECT unlikely(1);
+ | ---
+ | - metadata:
+ |   - name: unlikely(1)
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT unlikely(1, 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to UNLIKELY(): expected 1, got 2'
+ | ...
+
+-- Function upper
+SELECT upper();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to UPPER(): expected 1, got 0'
+ | ...
+SELECT upper('a');
+ | ---
+ | - metadata:
+ |   - name: upper('a')
+ |     type: string
+ |   rows:
+ |   - ['A']
+ | ...
+SELECT upper('a', 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to UPPER(): expected 1, got 2'
+ | ...
+
+-- Function version
+SELECT typeof(version());
+ | ---
+ | - metadata:
+ |   - name: typeof(version())
+ |     type: string
+ |   rows:
+ |   - ['string']
+ | ...
+SELECT typeof(version(1));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to VERSION(): expected 0, got 1'
+ | ...
+
+-- Function zeroblob
+SELECT zeroblob();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to ZEROBLOB(): expected 1, got 0'
+ | ...
+SELECT zeroblob(1);
+ | ---
+ | - metadata:
+ |   - name: zeroblob(1)
+ |     type: varbinary
+ |   rows:
+ |   - ["\0"]
+ | ...
+SELECT zeroblob(1, 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to ZEROBLOB(): expected 1, got 2'
+ | ...
+
+-- Function avg
+SELECT avg() FROM (values(1), (2), (3));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to AVG(): expected 1, got 0'
+ | ...
+SELECT avg("_auto_field_") FROM (values(1), (2), (3));
+ | ---
+ | - metadata:
+ |   - name: avg("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [2]
+ | ...
+SELECT avg("_auto_field_", 2) FROM (values(1), (2), (3));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to AVG(): expected 1, got 2'
+ | ...
+
+-- Function count
+SELECT count() FROM (values(1), (2), (3));
+ | ---
+ | - metadata:
+ |   - name: count()
+ |     type: integer
+ |   rows:
+ |   - [3]
+ | ...
+SELECT count("_auto_field_") FROM (values(1), (2), (3));
+ | ---
+ | - metadata:
+ |   - name: count("_auto_field_")
+ |     type: integer
+ |   rows:
+ |   - [3]
+ | ...
+SELECT count("_auto_field_", 2) FROM (values(1), (2), (3));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to COUNT(): expected from 0 to 1, got 2'
+ | ...
+
+-- Function group_concat
+SELECT group_concat() FROM (values('1'), ('2'), ('3'));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to GROUP_CONCAT(): expected from 1 to 2, got
+ |   0'
+ | ...
+SELECT group_concat("_auto_field_") FROM (values('1'), ('2'), ('3'));
+ | ---
+ | - metadata:
+ |   - name: group_concat("_auto_field_")
+ |     type: string
+ |   rows:
+ |   - ['1,2,3']
+ | ...
+SELECT group_concat("_auto_field_", '2') FROM (values('1'), ('2'), ('3'));
+ | ---
+ | - metadata:
+ |   - name: group_concat("_auto_field_", '2')
+ |     type: string
+ |   rows:
+ |   - ['12223']
+ | ...
+SELECT group_concat("_auto_field_", '2', '3') FROM (values('1'), ('2'), ('3'));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to GROUP_CONCAT(): expected from 1 to 2, got
+ |   3'
+ | ...
+
+-- Function max
+SELECT max() FROM (values(1), (2), (3));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to MAX(): expected 1, got 0'
+ | ...
+SELECT max("_auto_field_") FROM (values(1), (2), (3));
+ | ---
+ | - metadata:
+ |   - name: max("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [3]
+ | ...
+SELECT max("_auto_field_", 2) FROM (values(1), (2), (3));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to MAX(): expected 1, got 2'
+ | ...
+
+-- Function min
+SELECT min() FROM (values(1), (2), (3));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to MIN(): expected 1, got 0'
+ | ...
+SELECT min("_auto_field_") FROM (values(1), (2), (3));
+ | ---
+ | - metadata:
+ |   - name: min("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [1]
+ | ...
+SELECT min("_auto_field_", 2) FROM (values(1), (2), (3));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to MIN(): expected 1, got 2'
+ | ...
+
+-- Function sum
+SELECT sum() FROM (values(1), (2), (3));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SUM(): expected 1, got 0'
+ | ...
+SELECT sum("_auto_field_") FROM (values(1), (2), (3));
+ | ---
+ | - metadata:
+ |   - name: sum("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [6]
+ | ...
+SELECT sum("_auto_field_", 2) FROM (values(1), (2), (3));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SUM(): expected 1, got 2'
+ | ...
+
+-- Function total
+SELECT total() FROM (values(1), (2), (3));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to TOTAL(): expected 1, got 0'
+ | ...
+SELECT total("_auto_field_") FROM (values(1), (2), (3));
+ | ---
+ | - metadata:
+ |   - name: total("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [6]
+ | ...
+SELECT total("_auto_field_", 2) FROM (values(1), (2), (3));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to TOTAL(): expected 1, got 2'
+ | ...
diff --git a/test/sql/gh-4159-function-argumens.test.sql b/test/sql/gh-4159-function-argumens.test.sql
new file mode 100644
index 000000000..7578768cb
--- /dev/null
+++ b/test/sql/gh-4159-function-argumens.test.sql
@@ -0,0 +1,193 @@
+-- Function abs().
+SELECT abs();
+SELECT abs(1);
+SELECT abs(1, 2);
+
+-- Function char().
+SELECT char();
+SELECT char(1);
+SELECT char(1, 2);
+
+-- Function character_length().
+SELECT character_length();
+SELECT character_length('1');
+SELECT character_length('1', '2');
+
+-- Function char_length().
+SELECT char_length();
+SELECT char_length('1');
+SELECT char_length('1', '2');
+
+-- Function coalesce().
+SELECT coalesce();
+SELECT coalesce('1');
+SELECT coalesce('1', '2');
+SELECT coalesce('1', '2', '3');
+
+-- Function greatest().
+SELECT greatest();
+SELECT greatest('1');
+SELECT greatest('1', '2');
+
+-- Function hex().
+SELECT hex();
+SELECT hex(X'33');
+SELECT hex(X'33', X'33');
+
+-- Function ifnull
+SELECT ifnull();
+SELECT ifnull(1);
+SELECT ifnull(1, 2);
+SELECT ifnull(1, 2, 3);
+
+-- Function least().
+SELECT least();
+SELECT least('1');
+SELECT least('1', '2');
+
+-- Function length().
+SELECT length();
+SELECT length('1');
+SELECT length('1', '2');
+
+-- Function likelihood
+SELECT likelihood();
+SELECT likelihood(1);
+SELECT likelihood(1, 0.5);
+SELECT likelihood(1, 0.5, 3);
+
+-- Function likely
+SELECT likely();
+SELECT likely(1);
+SELECT likely(1, 2);
+
+-- Function lower
+SELECT lower();
+SELECT lower('a');
+SELECT lower('a', 2);
+
+-- Function nullif
+SELECT nullif();
+SELECT nullif(1);
+SELECT nullif(1, 2);
+SELECT nullif(1, 2, 3);
+
+-- Function position
+SELECT position();
+SELECT position('12345');
+SELECT position('12345', '2');
+SELECT position('12345', '2', 3);
+
+-- Function printf
+SELECT printf();
+SELECT printf('1');
+SELECT printf('1', 2);
+SELECT printf('1', 2, 3);
+
+-- Function quote
+SELECT quote();
+SELECT quote('1');
+SELECT quote('1', 2);
+
+-- Function random
+SELECT typeof(random());
+SELECT typeof(random(1));
+
+-- Function randomblob
+SELECT typeof(randomblob());
+SELECT typeof(randomblob(1));
+SELECT typeof(randomblob(1, 2));
+
+-- Function replace
+SELECT replace();
+SELECT replace('12345');
+SELECT replace('12345', '2');
+SELECT replace('12345', '2', '3');
+SELECT replace('12345', '2', '3', 4);
+
+-- Function round
+SELECT round();
+SELECT round(1.1245);
+SELECT round(1.1245, 2);
+SELECT round(1.1245, 2, 3);
+
+-- Function row_count
+SELECT row_count();
+SELECT row_count(1);
+
+-- Function soundex
+SELECT soundex();
+SELECT soundex(1);
+SELECT soundex(1, 2);
+
+-- Function substr
+SELECT substr();
+SELECT substr('12345');
+SELECT substr('12345', 2);
+SELECT substr('12345', 2, 3);
+SELECT substr('12345', 2, 3, 4);
+
+-- Function typeof
+SELECT typeof();
+SELECT typeof(1);
+SELECT typeof(1, 2);
+
+-- Function unicode
+SELECT unicode();
+SELECT unicode('1');
+SELECT unicode('1', '2');
+
+-- Function unlikely
+SELECT unlikely();
+SELECT unlikely(1);
+SELECT unlikely(1, 2);
+
+-- Function upper
+SELECT upper();
+SELECT upper('a');
+SELECT upper('a', 2);
+
+-- Function version
+SELECT typeof(version());
+SELECT typeof(version(1));
+
+-- Function zeroblob
+SELECT zeroblob();
+SELECT zeroblob(1);
+SELECT zeroblob(1, 2);
+
+-- Function avg
+SELECT avg() FROM (values(1), (2), (3));
+SELECT avg("_auto_field_") FROM (values(1), (2), (3));
+SELECT avg("_auto_field_", 2) FROM (values(1), (2), (3));
+
+-- Function count
+SELECT count() FROM (values(1), (2), (3));
+SELECT count("_auto_field_") FROM (values(1), (2), (3));
+SELECT count("_auto_field_", 2) FROM (values(1), (2), (3));
+
+-- Function group_concat
+SELECT group_concat() FROM (values('1'), ('2'), ('3'));
+SELECT group_concat("_auto_field_") FROM (values('1'), ('2'), ('3'));
+SELECT group_concat("_auto_field_", '2') FROM (values('1'), ('2'), ('3'));
+SELECT group_concat("_auto_field_", '2', '3') FROM (values('1'), ('2'), ('3'));
+
+-- Function max
+SELECT max() FROM (values(1), (2), (3));
+SELECT max("_auto_field_") FROM (values(1), (2), (3));
+SELECT max("_auto_field_", 2) FROM (values(1), (2), (3));
+
+-- Function min
+SELECT min() FROM (values(1), (2), (3));
+SELECT min("_auto_field_") FROM (values(1), (2), (3));
+SELECT min("_auto_field_", 2) FROM (values(1), (2), (3));
+
+-- Function sum
+SELECT sum() FROM (values(1), (2), (3));
+SELECT sum("_auto_field_") FROM (values(1), (2), (3));
+SELECT sum("_auto_field_", 2) FROM (values(1), (2), (3));
+
+-- Function total
+SELECT total() FROM (values(1), (2), (3));
+SELECT total("_auto_field_") FROM (values(1), (2), (3));
+SELECT total("_auto_field_", 2) FROM (values(1), (2), (3));
-- 
2.25.1

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [Tarantool-patches] [PATCH v4 4/5] sql: change implicit cast for assignment
  2020-07-13  5:32 [Tarantool-patches] [PATCH v4 0/5] Change implicit cast for assignment imeevma
                   ` (2 preceding siblings ...)
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 3/5] sql: check number of arguments in sql_func_by_signature() imeevma
@ 2020-07-13  5:33 ` imeevma
  2020-07-13 14:42   ` Nikita Pettik
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 5/5] sql: properly check arguments of functions imeevma
  4 siblings, 1 reply; 11+ messages in thread
From: imeevma @ 2020-07-13  5:33 UTC (permalink / raw)
  To: korablev, tsafin, tarantool-patches

This patch changes implicit cast for assignment.

Closes #3809
Needed for #4159
Part of #4230

@TarantoolBot document
Title: change implicit cast for comparison

After this patch, these rules will apply during the implicit cast:

| | STRING | BINARY | BOOLEAN | INTEGER | UNSIGNED | DOUBLE |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: |
| STRING | A | N | N | N | N | N |
| BINARY | N | A | N | N | N | N |
| BOOLEAN | N | N | A | N | N | N |
| INTEGER | N | N | N | A | S | Aa |
| UNSIGNED | N | N | N | A | A | Aa |
| DOUBLE | N | N | N | Sa | Sa | A |

In this table, the types of the assigned value are on the left,
and the types that should be after the assignment are at the top.

'A' - the assignment will be completed.
'N' - the assignment won't be completed.
'S' - the appointment may be unsuccessful.
'a' - after assignment, the resulting value may be approximated.

Rules for numeric values are these:
1) Loss of significant digits (overflow) is an error.
2) Loss of insignificant digits is not an error.

Example:
```
tarantool> box.execute([[CREATE TABLE t1(i INT PRIMARY KEY);]])
tarantool> box.execute([[INSERT INTO t1 VALUES ('1');]])
---
- null
- 'Type mismatch: can not convert 1 to integer'
...

tarantool> box.execute('INSERT INTO t1 VALUES (1.2345);')
tarantool> box.execute('SELECT * FROM t1;')
---
- metadata:
  - name: I
    type: integer
  rows:
  - [1]
...

tarantool> box.execute([[CREATE TABLE t2(t text PRIMARY KEY);]])
tarantool> box.execute([[INSERT INTO t2 VALUES (1);]])
---
- null
- 'Type mismatch: can not convert 1 to string'
...
```
---
 src/box/sql/vdbe.c                   | 156 ++++-
 src/box/sql/vdbeInt.h                |   4 +
 test/sql-tap/autoinc.test.lua        |   2 +-
 test/sql-tap/default.test.lua        |   6 +-
 test/sql-tap/e_select1.test.lua      |  27 +-
 test/sql-tap/in3.test.lua            |  14 +-
 test/sql-tap/in4.test.lua            |  96 +--
 test/sql-tap/index1.test.lua         |  24 +-
 test/sql-tap/insert3.test.lua        |  10 +-
 test/sql-tap/intpkey.test.lua        | 133 +---
 test/sql-tap/limit.test.lua          |   2 +-
 test/sql-tap/minmax2.test.lua        |   6 +-
 test/sql-tap/misc1.test.lua          |  24 +-
 test/sql-tap/numcast.test.lua        |   6 +-
 test/sql-tap/select1.test.lua        |   8 +-
 test/sql-tap/select4.test.lua        |  12 +-
 test/sql-tap/select7.test.lua        |   2 +-
 test/sql-tap/sort.test.lua           |   8 +-
 test/sql-tap/subquery.test.lua       |  69 +-
 test/sql-tap/tkt-3998683a16.test.lua |  26 +-
 test/sql-tap/tkt-54844eea3f.test.lua |   8 +-
 test/sql-tap/tkt-7bbfb7d442.test.lua |   4 +-
 test/sql-tap/tkt-9a8b09f8e6.test.lua |  62 +-
 test/sql-tap/tkt-f973c7ac31.test.lua |  18 +-
 test/sql-tap/tkt-fc7bd6358f.test.lua | 111 ----
 test/sql-tap/tkt1444.test.lua        |   4 +-
 test/sql-tap/tkt3493.test.lua        |   6 +-
 test/sql-tap/tkt3841.test.lua        |  12 +-
 test/sql-tap/transitive1.test.lua    |   4 +-
 test/sql-tap/triggerA.test.lua       |   4 +-
 test/sql-tap/unique.test.lua         |   8 +-
 test/sql-tap/view.test.lua           |   2 +-
 test/sql-tap/where5.test.lua         |  10 +-
 test/sql-tap/whereB.test.lua         | 908 ---------------------------
 test/sql-tap/whereC.test.lua         |   8 +-
 test/sql/types.result                | 620 ++++++++++++++++++
 test/sql/types.test.lua              | 129 ++++
 37 files changed, 1042 insertions(+), 1511 deletions(-)
 delete mode 100755 test/sql-tap/tkt-fc7bd6358f.test.lua
 delete mode 100755 test/sql-tap/whereB.test.lua

diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 950f72ddd..863f38f5d 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -417,6 +417,144 @@ sql_value_apply_type(
 	mem_apply_type((Mem *) pVal, type);
 }
 
+/**
+ * Check that MEM_type of the mem is compatible with given type.
+ *
+ * @param mem The MEM that contains the value to check.
+ * @param type The type to check.
+ * @retval TRUE if the MEM_type of the value and the given type
+ *         are compatible, FALSE otherwise.
+ */
+static bool
+mem_is_type_compatible(struct Mem *mem, enum field_type type)
+{
+	enum mp_type mp_type = mem_mp_type(mem);
+	assert(mp_type < MP_EXT);
+	return field_mp_plain_type_is_compatible(type, mp_type, true);
+}
+
+/**
+ * Convert the numeric value contained in MEM to double. If the
+ * is_precise flag is set, the conversion will succeed only if it
+ * is lossless.
+ *
+ * @param mem The MEM that contains the numeric value.
+ * @param is_precise Flag.
+ * @retval 0 if the conversion was successful, -1 otherwise.
+ */
+static int
+mem_convert_to_double(struct Mem *mem, bool is_precise)
+{
+	if ((mem->flags & MEM_Real) != 0)
+		return 0;
+	if ((mem->flags & (MEM_Int | MEM_UInt)) == 0)
+		return -1;
+	if ((mem->flags & MEM_Int) != 0) {
+		int64_t i = mem->u.i;
+		double d = (double)i;
+		if (!is_precise || i == (int64_t)d)
+			mem_set_double(mem, d);
+		else
+			return -1;
+	} else {
+		uint64_t u = mem->u.u;
+		double d = (double)u;
+		if (!is_precise || u == (uint64_t)d)
+			mem_set_double(mem, d);
+		else
+			return -1;
+	}
+	return 0;
+}
+
+/**
+ * Convert the numeric value contained in MEM to unsigned. If the
+ * is_precise flag is set, the conversion will succeed only if it
+ * is lossless.
+ *
+ * @param mem The MEM that contains the numeric value.
+ * @param is_precise Flag.
+ * @retval 0 if the conversion was successful, -1 otherwise.
+ */
+static int
+mem_convert_to_unsigned(struct Mem *mem, bool is_precise)
+{
+	if ((mem->flags & MEM_UInt) != 0)
+		return 0;
+	if ((mem->flags & MEM_Int) != 0)
+		return -1;
+	if ((mem->flags & MEM_Real) == 0)
+		return -1;
+	double d = mem->u.r;
+	if (d < 0.0 || d >= (double)UINT64_MAX)
+		return -1;
+	uint64_t u = (uint64_t)d;
+	if (!is_precise || d == (double)u)
+		mem_set_u64(mem, (uint64_t) d);
+	else
+		return -1;
+	return 0;
+}
+
+/**
+ * Convert the numeric value contained in MEM to integer. If the
+ * is_precise flag is set, the conversion will succeed only if it
+ * is lossless.
+ *
+ * @param mem The MEM that contains the numeric value.
+ * @param is_precise Flag.
+ * @retval 0 if the conversion was successful, -1 otherwise.
+ */
+static int
+mem_convert_to_integer(struct Mem *mem, bool is_precise)
+{
+	if ((mem->flags & (MEM_UInt | MEM_Int)) != 0)
+		return 0;
+	if ((mem->flags & MEM_Real) == 0)
+		return -1;
+	double d = mem->u.r;
+	if (d >= (double)UINT64_MAX || d < (double)INT64_MIN)
+		return -1;
+	if (d < (double)INT64_MAX) {
+		int64_t i = (int64_t)d;
+		if (!is_precise || d == (double)i)
+			mem_set_int(mem, (int64_t) d, d < 0);
+		else
+			return -1;
+	} else {
+		uint64_t u = (uint64_t)d;
+		if (!is_precise || d == (double)u)
+			mem_set_int(mem, (uint64_t) d, false);
+		else
+			return -1;
+	}
+	return 0;
+}
+
+/**
+ * Convert the numeric value contained in MEM to another numeric
+ * type. If the is_precise flag is set, the conversion will
+ * succeed only if it is lossless.
+ *
+ * @param mem The MEM that contains the numeric value.
+ * @param type The type to convert to.
+ * @param is_precise Flag.
+ * @retval 0 if the conversion was successful, -1 otherwise.
+ */
+static int
+mem_convert_to_numeric(struct Mem *mem, enum field_type type, bool is_precise)
+{
+	assert(mp_type_is_numeric(mem_mp_type(mem)) &&
+	       sql_type_is_numeric(type));
+	assert(type != FIELD_TYPE_NUMBER);
+	if (type == FIELD_TYPE_DOUBLE)
+		return mem_convert_to_double(mem, is_precise);
+	if (type == FIELD_TYPE_UNSIGNED)
+		return mem_convert_to_unsigned(mem, is_precise);
+	assert(type == FIELD_TYPE_INTEGER);
+	return mem_convert_to_integer(mem, is_precise);
+}
+
 /*
  * pMem currently only holds a string type (or maybe a BLOB that we can
  * interpret as a string if we want to).  Compute its corresponding
@@ -2747,11 +2885,11 @@ case OP_Fetch: {
 /* Opcode: ApplyType P1 P2 * P4 *
  * Synopsis: type(r[P1@P2])
  *
- * Apply types to a range of P2 registers starting with P1.
- *
- * P4 is a string that is P2 characters long. The nth character of the
- * string indicates the column type that should be used for the nth
- * memory cell in the range.
+ * Check that types of P2 registers starting from register P1 are
+ * compatible with given field types in P4. If the MEM_type of the
+ * value and the given type are incompatible according to
+ * field_mp_plain_type_is_compatible(), but both are numeric,
+ * this opcode attempts to convert the value to the type.
  */
 case OP_ApplyType: {
 	enum field_type *types = pOp->p4.types;
@@ -2762,7 +2900,13 @@ case OP_ApplyType: {
 	while((type = *(types++)) != field_type_MAX) {
 		assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
 		assert(memIsValid(pIn1));
-		if (mem_apply_type(pIn1, type) != 0) {
+		if (mem_is_type_compatible(pIn1, type)) {
+			pIn1++;
+			continue;
+		}
+		if (!mp_type_is_numeric(mem_mp_type(pIn1)) ||
+		    !sql_type_is_numeric(type) ||
+		    mem_convert_to_numeric(pIn1, type, false) != 0) {
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
 				 sql_value_to_diag_str(pIn1),
 				 field_type_strs[type]);
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 44c27bdb7..ad46ab129 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -566,6 +566,10 @@ mem_mp_type(struct Mem *mem);
  */
 #define mp_type_is_bloblike(X) ((X) == MP_BIN || (X) == MP_ARRAY || (X) == MP_MAP)
 
+/** Return TRUE if MP_type of X is numeric, FALSE otherwise. */
+#define mp_type_is_numeric(X) ((X) == MP_INT || (X) == MP_UINT ||\
+			       (X) == MP_DOUBLE)
+
 /**
  * Memory cell mem contains the context of an aggregate function.
  * This routine calls the finalize method for that function. The
diff --git a/test/sql-tap/autoinc.test.lua b/test/sql-tap/autoinc.test.lua
index 37e65e541..07442b60a 100755
--- a/test/sql-tap/autoinc.test.lua
+++ b/test/sql-tap/autoinc.test.lua
@@ -694,7 +694,7 @@ test:do_test(
                  INSERT INTO t3928(b) VALUES('after-int-' || CAST(new.b AS TEXT));
             END;
             DELETE FROM t3928 WHERE a!=1;
-            UPDATE t3928 SET b=456 WHERE a=1;
+            UPDATE t3928 SET b='456' WHERE a=1;
             SELECT * FROM t3928 ORDER BY a;
         ]])
     end, {
diff --git a/test/sql-tap/default.test.lua b/test/sql-tap/default.test.lua
index d3e35c71c..f1def2b10 100755
--- a/test/sql-tap/default.test.lua
+++ b/test/sql-tap/default.test.lua
@@ -109,16 +109,12 @@ test:do_execsql_test(
 	f VARCHAR(15), --COLLATE RTRIM,
 	g INTEGER DEFAULT( 3600*12 )
 	);
-	INSERT INTO t3 VALUES(null, 5, 'row1', 5.25, 8.67, 321, 432);
+	INSERT INTO t3 VALUES(null, 5, 'row1', 5.25, 8.67, '321', 432);
 	SELECT a, typeof(a), b, typeof(b), c, typeof(c), 
 	d, typeof(d), e, typeof(e), f, typeof(f),
 	g, typeof(g) FROM t3;
 	]], {
 	-- <default-3.1>
-	-- TODO: In original test "321" is not a string, its a value.
-	-- In current situation I don't know what to do, need Kirill's
-	-- advice.
-	-- Bulat
 	1, "integer", 5, "integer", "row1", "string", 5.25, "number", 8.67, "number", "321", "string", 432, "integer"
 	-- </default-3.1>
 })
diff --git a/test/sql-tap/e_select1.test.lua b/test/sql-tap/e_select1.test.lua
index 1d3b964b9..578620fca 100755
--- a/test/sql-tap/e_select1.test.lua
+++ b/test/sql-tap/e_select1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(510)
+test:plan(509)
 
 --!./tcltestrunner.lua
 -- 2010 July 16
@@ -753,11 +753,11 @@ test:do_execsql_test(
         CREATE TABLE z3(id  INT primary key, a NUMBER, b NUMBER);
 
         INSERT INTO z1 VALUES(1, 51.65, -59.58, 'belfries');
-        INSERT INTO z1 VALUES(2, -5, NULL, 75);
+        INSERT INTO z1 VALUES(2, -5, NULL, '75');
         INSERT INTO z1 VALUES(3, -2.2, -23.18, 'suiters');
         INSERT INTO z1 VALUES(4, NULL, 67, 'quartets');
         INSERT INTO z1 VALUES(5, -1.04, -32.3, 'aspen');
-        INSERT INTO z1 VALUES(6, 63, '0', -26);
+        INSERT INTO z1 VALUES(6, 63, 0, '-26');
 
         INSERT INTO z2 VALUES(1, NULL, 21);
         INSERT INTO z2 VALUES(2, 36.0, 6.0);
@@ -1457,13 +1457,13 @@ test:do_execsql_test(
         CREATE TABLE q2(id  INT primary key, d TEXT, e NUMBER);
         CREATE TABLE q3(id  INT primary key, f TEXT, g INT);
 
-        INSERT INTO q1 VALUES(1, 16, -87.66, NULL);
+        INSERT INTO q1 VALUES(1, '16', -87.66, NULL);
         INSERT INTO q1 VALUES(2, 'legible', 94, -42.47);
         INSERT INTO q1 VALUES(3, 'beauty', 36, NULL);
 
         INSERT INTO q2 VALUES(1, 'legible', 1);
         INSERT INTO q2 VALUES(2, 'beauty', 2);
-        INSERT INTO q2 VALUES(3, -65, 4);
+        INSERT INTO q2 VALUES(3, '-65', 4);
         INSERT INTO q2 VALUES(4, 'emanating', -16.56);
 
         INSERT INTO q3 VALUES(1, 'beauty', 2);
@@ -1603,7 +1603,7 @@ test:do_execsql_test(
         CREATE TABLE w2(a  INT PRIMARY KEY, b TEXT);
 
         INSERT INTO w1 VALUES('1', 4.1);
-        INSERT INTO w2 VALUES(1, 4.1);
+        INSERT INTO w2 VALUES(1, '4.1');
     ]], {
         -- <e_select-7.10.0>
 
@@ -2150,7 +2150,6 @@ test:do_select_tests(
         {"2", "SELECT b FROM f1 ORDER BY a LIMIT 2+3 ", {"a",  "b", "c", "d", "e"}},
         {"3", "SELECT b FROM f1 ORDER BY a LIMIT (SELECT a FROM f1 WHERE b = 'e') ", {"a",  "b", "c", "d", "e"}},
         {"4", "SELECT b FROM f1 ORDER BY a LIMIT 5.0 ", {"a",  "b", "c", "d", "e"}},
-        {"5", "SELECT b FROM f1 ORDER BY a LIMIT '5' ", {"a",  "b", "c", "d", "e"}},
     })
 
 -- EVIDENCE-OF: R-46155-47219 If the expression evaluates to a NULL value
@@ -2195,7 +2194,7 @@ test:do_select_tests(
         {"1", "SELECT b FROM f1 ORDER BY a LIMIT 0 ", {}},
         {"2", "SELECT b FROM f1 ORDER BY a DESC LIMIT 4 ", {"z", "y", "x", "w"}},
         {"3", "SELECT b FROM f1 ORDER BY a DESC LIMIT 8 ", {"z", "y", "x", "w", "v", "u", "t", "s"}},
-        {"4", "SELECT b FROM f1 ORDER BY a DESC LIMIT '12' ", {"z", y, "x", "w", "v", "u", "t", "s", "r", "q", "p", "o"}},
+        {"4", "SELECT b FROM f1 ORDER BY a DESC LIMIT 12 ", {"z", y, "x", "w", "v", "u", "t", "s", "r", "q", "p", "o"}},
     })
 
 -- EVIDENCE-OF: R-54935-19057 Or, if the SELECT statement would return
@@ -2240,10 +2239,10 @@ test:do_select_tests(
         {"1", "SELECT b FROM f1 ORDER BY a LIMIT 10 OFFSET 5", {"f", "g", "h", "i", "j", "k", "l", "m", "n", "o"}},
         {"2", "SELECT b FROM f1 ORDER BY a LIMIT 2+3 OFFSET 10", {"k", "l", "m", "n", "o"}},
         {"3", "SELECT b FROM f1 ORDER BY a LIMIT  (SELECT a FROM f1 WHERE b='j') OFFSET (SELECT a FROM f1 WHERE b='b') ", {"c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}},
-        {"4", "SELECT b FROM f1 ORDER BY a LIMIT '5' OFFSET 3.0 ", {"d", "e", "f", "g", "h"}},
-        {"5", "SELECT b FROM f1 ORDER BY a LIMIT '5' OFFSET 0 ", {"a", "b", "c", "d", "e"}},
+        {"4", "SELECT b FROM f1 ORDER BY a LIMIT 5 OFFSET 3.0 ", {"d", "e", "f", "g", "h"}},
+        {"5", "SELECT b FROM f1 ORDER BY a LIMIT 5 OFFSET 0 ", {"a", "b", "c", "d", "e"}},
         {"6", "SELECT b FROM f1 ORDER BY a LIMIT 0 OFFSET 10 ", {}},
-        {"7", "SELECT b FROM f1 ORDER BY a LIMIT 3 OFFSET '1'||'5' ", {"p", "q", "r"}},
+        {"7", "SELECT b FROM f1 ORDER BY a LIMIT 3 OFFSET CAST('1'||'5' AS INTEGER) ", {"p", "q", "r"}},
     })
 
 -- EVIDENCE-OF: R-34648-44875 Or, if the SELECT would return less than
@@ -2279,10 +2278,10 @@ test:do_select_tests(
         {"1", "SELECT b FROM f1 ORDER BY a LIMIT 5, 10 ", {"f", "g", "h", "i", "j", "k", "l", "m", "n", "o"}},
         {"2", "SELECT b FROM f1 ORDER BY a LIMIT 10, 2+3 ", {"k", "l", "m", "n", "o"}},
         {"3", "SELECT b FROM f1 ORDER BY a LIMIT (SELECT a FROM f1 WHERE b='b'), (SELECT a FROM f1 WHERE b='j')", {"c", "d", "e", "f", "g", "h", "i", "j", "k", "l"}},
-        {"4", "SELECT b FROM f1 ORDER BY a LIMIT 3.0, '5' ", {"d", "e", "f", "g", "h"}},
-        {"5", "SELECT b FROM f1 ORDER BY a LIMIT 0, '5' ", {"a", "b", "c", "d", "e"}},
+        {"4", "SELECT b FROM f1 ORDER BY a LIMIT 3.0, 5 ", {"d", "e", "f", "g", "h"}},
+        {"5", "SELECT b FROM f1 ORDER BY a LIMIT 0, 5 ", {"a", "b", "c", "d", "e"}},
         {"6", "SELECT b FROM f1 ORDER BY a LIMIT 10, 0 ", {}},
-        {"7", "SELECT b FROM f1 ORDER BY a LIMIT '1'||'5', 3 ", {"p", "q", "r"}},
+        {"7", "SELECT b FROM f1 ORDER BY a LIMIT CAST('1'||'5' AS INTEGER), 3 ", {"p", "q", "r"}},
         {"8", "SELECT b FROM f1 ORDER BY a LIMIT 20, 10 ", {"u", "v", "w", "x", "y", "z"}},
         {"9", "SELECT a FROM f1 ORDER BY a DESC LIMIT 18+4, 100 ", {4, 3, 2, 1}},
         {"10", "SELECT b FROM f1 ORDER BY a LIMIT 0, 5 ", {"a", "b", "c", "d", "e"}},
diff --git a/test/sql-tap/in3.test.lua b/test/sql-tap/in3.test.lua
index e29db9d93..a6d842962 100755
--- a/test/sql-tap/in3.test.lua
+++ b/test/sql-tap/in3.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(29)
+test:plan(28)
 
 --!./tcltestrunner.lua
 -- 2007 November 29
@@ -322,18 +322,6 @@ test:do_test(
         -- </in3-3.2>
     })
 
-test:do_test(
-    "in3-3.3",
-    function()
-        -- Logically, numeric affinity is applied to both sides before
-        -- the comparison, but index can't be used.
-        return exec_neph(" SELECT x IN (SELECT b FROM t1) FROM t2 ")
-    end, {
-        -- <in3-3.3>
-        1, true
-        -- </in3-3.3>
-    })
-
 test:do_test(
     "in3-3.4",
     function()
diff --git a/test/sql-tap/in4.test.lua b/test/sql-tap/in4.test.lua
index 8c6917379..5c01ccdab 100755
--- a/test/sql-tap/in4.test.lua
+++ b/test/sql-tap/in4.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(61)
+test:plan(52)
 
 --!./tcltestrunner.lua
 -- 2008 September 1
@@ -140,7 +140,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "in4-2.7",
     [[
-        SELECT b FROM t2 WHERE a IN ('1', '2') 
+        SELECT b FROM t2 WHERE a IN (1, 2)
     ]], {
         -- <in4-2.7>
         "one", "two"
@@ -585,98 +585,6 @@ test:do_execsql_test(
         -- </in4-4.6>
     })
 
-test:do_execsql_test(
-    "in4-4.11",
-    [[
-        CREATE TABLE t4b(a TEXT, b NUMBER, c  INT PRIMARY KEY);
-        INSERT INTO t4b VALUES('1.0',1,4);
-        SELECT c FROM t4b WHERE a=b;
-    ]], {
-        -- <in4-4.11>
-        4
-        -- </in4-4.11>
-    })
-
-test:do_execsql_test(
-    "in4-4.12",
-    [[
-        SELECT c FROM t4b WHERE b=a;
-    ]], {
-        -- <in4-4.12>
-        4
-        -- </in4-4.12>
-    })
-
-test:do_execsql_test(
-    "in4-4.13",
-    [[
-        SELECT c FROM t4b WHERE +a=b;
-    ]], {
-        -- <in4-4.13>
-        4
-        -- </in4-4.13>
-    })
-
-test:do_execsql_test(
-    "in4-4.14",
-    [[
-        SELECT c FROM t4b WHERE a=+b;
-    ]], {
-        -- <in4-4.14>
-        4
-        -- </in4-4.14>
-    })
-
-test:do_execsql_test(
-    "in4-4.15",
-    [[
-        SELECT c FROM t4b WHERE +b=a;
-    ]], {
-        -- <in4-4.15>
-        4
-        -- </in4-4.15>
-    })
-
-test:do_execsql_test(
-    "in4-4.16",
-    [[
-        SELECT c FROM t4b WHERE b=+a;
-    ]], {
-        -- <in4-4.16>
-        4
-        -- </in4-4.16>
-    })
-
-test:do_execsql_test(
-    "in4-4.17",
-    [[
-        SELECT c FROM t4b WHERE a IN (b);
-    ]], {
-        -- <in4-4.17>
-        4
-        -- </in4-4.17>
-    })
-
-test:do_execsql_test(
-    "in4-4.18",
-    [[
-        SELECT c FROM t4b WHERE b IN (a);
-    ]], {
-        -- <in4-4.18>
-        4
-        -- </in4-4.18>
-    })
-
-test:do_execsql_test(
-    "in4-4.19",
-    [[
-        SELECT c FROM t4b WHERE +b IN (a);
-    ]], {
-        -- <in4-4.19>
-        4
-        -- </in4-4.19>
-    })
-
 -- MUST_WORK_TEST
 -- Tarantool: TBI: Need to support collations. Depends on #2121
 -- test:do_execsql_test(
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index e173e685c..ce66b7c1e 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -593,25 +593,17 @@ test:do_test(
         -- </index-11.1>
     })
 end
--- integrity_check index-11.2
--- Numeric strings should compare as if they were numbers.  So even if the
--- strings are not character-by-character the same, if they represent the
--- same number they should compare equal to one another.  Verify that this
--- is true in indices.
---
--- Updated for sql v3: sql will now store these values as numbers
--- (because the affinity of column a is NUMERIC) so the quirky
--- representations are not retained. i.e. '+1.0' becomes '1'.
+
 test:do_execsql_test(
     "index-12.1",
     [[
         CREATE TABLE t4(id  INT primary key, a NUMBER,b INT );
-        INSERT INTO t4 VALUES(1, '0.0',1);
-        INSERT INTO t4 VALUES(2, '0.00',2);
-        INSERT INTO t4 VALUES(4, '-1.0',4);
-        INSERT INTO t4 VALUES(5, '+1.0',5);
-        INSERT INTO t4 VALUES(6, '0',6);
-        INSERT INTO t4 VALUES(7, '00000',7);
+        INSERT INTO t4 VALUES(1, 0.0, 1);
+        INSERT INTO t4 VALUES(2, 0.00, 2);
+        INSERT INTO t4 VALUES(4, -1.0, 4);
+        INSERT INTO t4 VALUES(5, +1.0, 5);
+        INSERT INTO t4 VALUES(6, 0, 6);
+        INSERT INTO t4 VALUES(7, 00000, 7);
         SELECT a FROM t4 ORDER BY b;
     ]], {
         -- <index-12.1>
@@ -692,7 +684,7 @@ test:do_execsql_test(
            c  TEXT,
            UNIQUE(a,c)
         );
-        INSERT INTO t5 VALUES(1,2,3);
+        INSERT INTO t5 VALUES(1,2,'3');
         SELECT * FROM t5;
     ]], {
         -- <index-13.1>
diff --git a/test/sql-tap/insert3.test.lua b/test/sql-tap/insert3.test.lua
index 43bb06630..b92bc508e 100755
--- a/test/sql-tap/insert3.test.lua
+++ b/test/sql-tap/insert3.test.lua
@@ -60,7 +60,7 @@ test:do_execsql_test(
             CREATE TABLE log2(rowid INTEGER PRIMARY KEY AUTOINCREMENT, x TEXT UNIQUE,y INT );
             CREATE TRIGGER r2 BEFORE INSERT ON t1 FOR EACH ROW BEGIN
               UPDATE log2 SET y=y+1 WHERE x=new.b;
-              INSERT OR IGNORE INTO log2(x, y) VALUES(new.b,1);
+              INSERT OR IGNORE INTO log2(x, y) VALUES(CAST(new.b AS STRING),1);
             END;
             INSERT INTO t1(a, b) VALUES('hi', 453);
             SELECT x,y FROM log ORDER BY x;
@@ -129,8 +129,8 @@ test:do_execsql_test(
               INSERT INTO t2dup(a,b,c) VALUES(new.a,new.b,new.c);
             END;
             INSERT INTO t2(a) VALUES(123);
-            INSERT INTO t2(b) VALUES(234);
-            INSERT INTO t2(c) VALUES(345);
+            INSERT INTO t2(b) VALUES('234');
+            INSERT INTO t2(c) VALUES('345');
             SELECT * FROM t2dup;
     ]], {
         -- <insert3-2.1>
@@ -143,8 +143,8 @@ test:do_execsql_test(
     [[
             DELETE FROM t2dup;
             INSERT INTO t2(a) SELECT 1 FROM t1 LIMIT 1;
-            INSERT INTO t2(b) SELECT 987 FROM t1 LIMIT 1;
-            INSERT INTO t2(c) SELECT 876 FROM t1 LIMIT 1;
+            INSERT INTO t2(b) SELECT '987' FROM t1 LIMIT 1;
+            INSERT INTO t2(c) SELECT '876' FROM t1 LIMIT 1;
             SELECT * FROM t2dup;
     ]], {
         -- <insert3-2.2>
diff --git a/test/sql-tap/intpkey.test.lua b/test/sql-tap/intpkey.test.lua
index b6b186632..684a24114 100755
--- a/test/sql-tap/intpkey.test.lua
+++ b/test/sql-tap/intpkey.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(40)
+test:plan(31)
 
 --!./tcltestrunner.lua
 -- 2001 September 15
@@ -770,142 +770,13 @@ test:do_execsql_test(
         -- </intpkey-11.1>
     })
 
--- integrity_check intpkey-12.1
--- Try to use a string that looks like a floating point number as
--- an integer primary key.  This should actually work when the floating
--- point value can be rounded to an integer without loss of data.
---
-test:do_execsql_test(
-    "intpkey-13.1",
-    [[
-        SELECT * FROM t1 WHERE a=1;
-    ]], {
-        -- <intpkey-13.1>
-        
-        -- </intpkey-13.1>
-    })
-
-test:do_execsql_test(
-    "intpkey-13.2",
-    [[
-        INSERT INTO t1 VALUES('1',2,3);
-        SELECT * FROM t1 WHERE a=1;
-    ]], {
-        -- <intpkey-13.2>
-        1, "2", "3"
-        -- </intpkey-13.2>
-    })
-
--- MUST_WORK_TEST
-if (0 > 0) then
-    -- Tarantool: issue submitted #2315
-    test:do_catchsql_test(
-        "intpkey-13.3",
-        [[
-            INSERT INTO t1 VALUES('1.5',3,4);
-        ]], {
-            -- <intpkey-13.3>
-            1, "datatype mismatch"
-            -- </intpkey-13.3>
-        })
-
-    test:do_catchsql_test(
-        "intpkey-13.4",
-        [[
-            INSERT INTO t1 VALUES(x'123456',3,4);
-        ]], {
-            -- <intpkey-13.4>
-            1, "datatype mismatch"
-            -- </intpkey-13.4>
-        })
-
-
-
-end
-test:do_catchsql_test(
-    "intpkey-13.5",
-    [[
-        INSERT INTO t1 VALUES('+1234567890',3,4);
-    ]], {
-        -- <intpkey-13.5>
-        0
-        -- </intpkey-13.5>
-    })
-
--- Compare an INTEGER PRIMARY KEY against a TEXT expression. The INTEGER
--- affinity should be applied to the text value before the comparison
--- takes place.
---
-test:do_execsql_test(
-    "intpkey-14.1",
-    [[
-        CREATE TABLE t3(a INTEGER PRIMARY KEY, b INTEGER, c TEXT);
-        INSERT INTO t3 VALUES(1, 1, 'one');
-        INSERT INTO t3 VALUES(2, 2, '2');
-        INSERT INTO t3 VALUES(3, 3, 3);
-    ]], {
-        -- <intpkey-14.1>
-        
-        -- </intpkey-14.1>
-    })
-
-test:do_execsql_test(
-    "intpkey-14.2",
-    [[
-        SELECT * FROM t3 WHERE a>2;
-    ]], {
-        -- <intpkey-14.2>
-        3, 3, "3"
-        -- </intpkey-14.2>
-    })
-
-test:do_execsql_test(
-    "intpkey-14.3",
-    [[
-        SELECT * FROM t3 WHERE a>'2';
-    ]], {
-        -- <intpkey-14.3>
-        3, 3, "3"
-        -- </intpkey-14.3>
-    })
-
-test:do_execsql_test(
-    "intpkey-14.4",
-    [[
-        SELECT * FROM t3 WHERE a<'2';
-    ]], {
-        -- <intpkey-14.4>
-        1, 1, "one"
-        -- </intpkey-14.4>
-    })
-
-test:do_execsql_test(
-    "intpkey-14.5",
-    [[
-        SELECT * FROM t3 WHERE a<c;
-    ]], {
-        -- <intpkey-14.5>
-        1, 1, "one"
-        -- </intpkey-14.5>
-    })
-
-test:do_execsql_test(
-    "intpkey-14.6",
-    [[
-        SELECT * FROM t3 WHERE a=c;
-    ]], {
-        -- <intpkey-14.6>
-        2, 2, "2", 3, 3, "3"
-        -- </intpkey-14.6>
-    })
-
 -- Check for proper handling of primary keys greater than 2^31.
 -- Ticket #1188
 --
 test:do_execsql_test(
     "intpkey-15.1",
     [[
-        INSERT INTO t1 VALUES(2147483647, 'big-1', 123);
+        INSERT INTO t1 VALUES(2147483647, 'big-1', '123');
         SELECT * FROM t1 WHERE a>2147483648;
     ]], {
         -- <intpkey-15.1>
diff --git a/test/sql-tap/limit.test.lua b/test/sql-tap/limit.test.lua
index 870233942..a7d1451f7 100755
--- a/test/sql-tap/limit.test.lua
+++ b/test/sql-tap/limit.test.lua
@@ -441,7 +441,7 @@ test:do_catchsql_test(
 test:do_execsql_test(
     "limit-6.5.2",
     [[
-        SELECT * FROM t6 LIMIT '12'
+        SELECT * FROM t6 LIMIT 12
     ]], {
     -- <limit-6.5>
     1, 2, 3, 4
diff --git a/test/sql-tap/minmax2.test.lua b/test/sql-tap/minmax2.test.lua
index 0e0f0d08e..707d1c4da 100755
--- a/test/sql-tap/minmax2.test.lua
+++ b/test/sql-tap/minmax2.test.lua
@@ -441,9 +441,9 @@ test:do_execsql_test(
     "minmax2-8.2",
     [[
         CREATE TABLE t5(a INTEGER PRIMARY KEY);
-        INSERT INTO t5 VALUES('1234');
-        INSERT INTO t5 VALUES('234');
-        INSERT INTO t5 VALUES('34');
+        INSERT INTO t5 VALUES(1234);
+        INSERT INTO t5 VALUES(234);
+        INSERT INTO t5 VALUES(34);
         SELECT min(a), max(a) FROM t5;
     ]], {
         -- <minmax2-8.2>
diff --git a/test/sql-tap/misc1.test.lua b/test/sql-tap/misc1.test.lua
index 32f38cc97..c0136d04c 100755
--- a/test/sql-tap/misc1.test.lua
+++ b/test/sql-tap/misc1.test.lua
@@ -34,9 +34,9 @@ test:do_test(
         end
         cmd = cmd .. ")"
         test:execsql(cmd)
-        cmd = "INSERT INTO manycol VALUES(1, 0"
+        cmd = "INSERT INTO manycol VALUES(1, '0'"
         for i = 1, 99, 1 do
-            cmd = cmd .. ","..i..""
+            cmd = cmd .. ",'"..i.."'"
         end
         cmd = cmd .. ")"
         test:execsql(cmd)
@@ -61,9 +61,9 @@ test:do_test(
     "misc1-1.3.1",
     function()
         for j = 100, 1000, 100 do
-            local cmd = string.format("INSERT INTO manycol VALUES(%s, %s", j, j)
+            local cmd = string.format("INSERT INTO manycol VALUES(%s, '%s'", j, j)
             for i = 1, 99, 1 do
-                cmd = cmd .. ","..(i + j)..""
+                cmd = cmd .. ",'"..(i + j).."'"
             end
             cmd = cmd .. ")"
             test:execsql(cmd)
@@ -176,7 +176,7 @@ test:do_test(
     "misc1-2.1",
     function()
         test:execsql([[
-            CREATE TABLE agger(one text primary key, two text, three text, four text);
+            CREATE TABLE agger(one int primary key, two text, three text, four text);
             START TRANSACTION;
             INSERT INTO agger VALUES(1, 'one', 'hello', 'yes');
             INSERT INTO agger VALUES(2, 'two', 'howdy', 'no');
@@ -531,7 +531,7 @@ test:do_test(
     "misc1-10.7",
     function()
         where = string.gsub(where, "x0=0", "x0=100")
-        return test:catchsql("UPDATE manycol SET x1=x1+1 "..where.."")
+        return test:catchsql("UPDATE manycol SET x1=CAST(x1+1 AS STRING) "..where.."")
     end, {
         -- <misc1-10.7>
         0
@@ -553,7 +553,7 @@ test:do_execsql_test(
 -- } {0 {}}
 test:do_execsql_test(
     "misc1-10.9",
-    "UPDATE manycol SET x1=x1+1 "..where
+    "UPDATE manycol SET x1=CAST(x1+1 AS STRING) "..where
         --"UPDATE manycol SET x1=x1+1 $::where AND rowid>0"
     , {})
 
@@ -665,7 +665,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "misc1-12.6",
     [[
-        INSERT OR IGNORE INTO t6 VALUES('y',0);
+        INSERT OR IGNORE INTO t6 VALUES('y','0');
         SELECT * FROM t6;
     ]], {
         -- <misc1-12.6>
@@ -679,10 +679,10 @@ test:do_execsql_test(
     "misc1-12.7",
     [[
         CREATE TABLE t7(x INTEGER, y TEXT, z  INT primary key);
-        INSERT INTO t7 VALUES(0,0,1);
-        INSERT INTO t7 VALUES(0.0,0,2);
-        INSERT INTO t7 VALUES(0,0.0,3);
-        INSERT INTO t7 VALUES(0.0,0.0,4);
+        INSERT INTO t7 VALUES(0,'0',1);
+        INSERT INTO t7 VALUES(0.0,'0',2);
+        INSERT INTO t7 VALUES(0,'0.0',3);
+        INSERT INTO t7 VALUES(0.0,'0.0',4);
         SELECT DISTINCT x, y FROM t7 ORDER BY z;
     ]], {
         -- <misc1-12.7>
diff --git a/test/sql-tap/numcast.test.lua b/test/sql-tap/numcast.test.lua
index eeac5353a..3161e48fa 100755
--- a/test/sql-tap/numcast.test.lua
+++ b/test/sql-tap/numcast.test.lua
@@ -135,16 +135,16 @@ test:do_catchsql_test(
         INSERT INTO t VALUES(20000000000000000000.01);
         SELECT * FROM t;
     ]], {
-        1,"Tuple field 1 type does not match one required by operation: expected integer"
+        1,"Type mismatch: can not convert 2.0e+19 to integer"
     })
 
-test:do_catchsql_test(
+test:do_execsql_test(
     "cast-2.9",
     [[
         INSERT INTO t VALUES(2.1);
         SELECT * FROM t;
     ]], {
-        1,"Tuple field 1 type does not match one required by operation: expected integer"
+        2, 9223372036854775808ULL, 18000000000000000000ULL
     })
 
 --
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index 6248abb65..0570dc49f 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -231,7 +231,7 @@ string.format([[
         CREATE TABLE t3(id INT, a TEXT, b TEXT, PRIMARY KEY(id));
         INSERT INTO t3 VALUES(1, 'abc',NULL);
         INSERT INTO t3 VALUES(2, NULL,'xyz');
-        INSERT INTO t3 SELECT f1, * FROM test1;
+        INSERT INTO t3 SELECT f1, CAST(f1 AS STRING), CAST(f2 AS STRING) FROM test1;
         DROP TABLE IF EXISTS t4;
         CREATE TABLE t4(id INT, a INT , b TEXT , PRIMARY KEY(id));
         INSERT INTO t4 VALUES(1, NULL,'%s');
@@ -1671,8 +1671,8 @@ test:do_execsql_test(
     [[
         DELETE FROM t3;
         DELETE FROM t4;
-        INSERT INTO t3 VALUES(0,1,2);
-        INSERT INTO t4 VALUES(0,3,4);
+        INSERT INTO t3 VALUES(0,'1','2');
+        INSERT INTO t4 VALUES(0,3,'4');
         SELECT * FROM t3, t4;
     ]], {
         -- <select1-11.1>
@@ -1878,7 +1878,7 @@ test:do_execsql_test(
     "select1-12.4",
     [[
         DELETE FROM t3;
-        INSERT INTO t3 VALUES(0,1,2);
+        INSERT INTO t3 VALUES(0,'1','2');
     ]], {
         -- <select1-12.4>
         
diff --git a/test/sql-tap/select4.test.lua b/test/sql-tap/select4.test.lua
index 23cf1bf1b..f7a320438 100755
--- a/test/sql-tap/select4.test.lua
+++ b/test/sql-tap/select4.test.lua
@@ -761,12 +761,12 @@ test:do_test(
         test:execsql [[
             CREATE TABLE t3(a text primary key, b NUMBER, c text);
             START TRANSACTION;
-            INSERT INTO t3 VALUES(1, 1.1, '1.1');
-            INSERT INTO t3 VALUES(2, 1.10, '1.10');
-            INSERT INTO t3 VALUES(3, 1.10, '1.1');
-            INSERT INTO t3 VALUES(4, 1.1, '1.10');
-            INSERT INTO t3 VALUES(5, 1.2, '1.2');
-            INSERT INTO t3 VALUES(6, 1.3, '1.3');
+            INSERT INTO t3 VALUES('1', 1.1, '1.1');
+            INSERT INTO t3 VALUES('2', 1.10, '1.10');
+            INSERT INTO t3 VALUES('3', 1.10, '1.1');
+            INSERT INTO t3 VALUES('4', 1.1, '1.10');
+            INSERT INTO t3 VALUES('5', 1.2, '1.2');
+            INSERT INTO t3 VALUES('6', 1.3, '1.3');
             COMMIT;
         ]]
         return test:execsql [[
diff --git a/test/sql-tap/select7.test.lua b/test/sql-tap/select7.test.lua
index fec5d7a41..e1e43c557 100755
--- a/test/sql-tap/select7.test.lua
+++ b/test/sql-tap/select7.test.lua
@@ -255,7 +255,7 @@ test:do_execsql_test(
     [[
         DROP TABLE IF EXISTS t5;
         CREATE TABLE t5(a TEXT primary key, b INT);
-        INSERT INTO t5 VALUES(123, 456);
+        INSERT INTO t5 VALUES('123', 456);
         SELECT typeof(a), a FROM t5 GROUP BY a HAVING a<b;
     ]], {
         -- <select7-7.7>
diff --git a/test/sql-tap/sort.test.lua b/test/sql-tap/sort.test.lua
index 36074d6ef..18bfd443d 100755
--- a/test/sql-tap/sort.test.lua
+++ b/test/sql-tap/sort.test.lua
@@ -505,10 +505,10 @@ test:do_execsql_test(
           a INTEGER PRIMARY KEY,
           b VARCHAR(30)
         );
-        INSERT INTO t4 VALUES(1,1);
-        INSERT INTO t4 VALUES(2,2);
-        INSERT INTO t4 VALUES(11,11);
-        INSERT INTO t4 VALUES(12,12);
+        INSERT INTO t4 VALUES(1,'1');
+        INSERT INTO t4 VALUES(2,'2');
+        INSERT INTO t4 VALUES(11,'11');
+        INSERT INTO t4 VALUES(12,'12');
         SELECT a FROM t4 ORDER BY 1;
     ]], {
         -- <sort-7.1>
diff --git a/test/sql-tap/subquery.test.lua b/test/sql-tap/subquery.test.lua
index 15c4c8276..e0771825e 100755
--- a/test/sql-tap/subquery.test.lua
+++ b/test/sql-tap/subquery.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(73)
+test:plan(69)
 
 --!./tcltestrunner.lua
 -- 2005 January 19
@@ -335,73 +335,6 @@ test:do_execsql_test(
         -- </subquery-2.4.3>
     })
 
-test:do_execsql_test(
-    "subquery-2.5.1",
-    [[
-        CREATE TABLE t3(a INTEGER PRIMARY KEY);
-        INSERT INTO t3 VALUES(10);
-
-        CREATE TABLE t4(x TEXT PRIMARY KEY);
-        INSERT INTO t4 VALUES('10');
-    ]], {
-        -- <subquery-2.5.1>
-        
-        -- </subquery-2.5.1>
-    })
-
-test:do_test(
-    "subquery-2.5.2",
-    function()
-        -- In the expr "x IN (SELECT a FROM t3)" the RHS of the IN operator
-        -- has text affinity and the LHS has integer affinity.  The rule is
-        -- that we try to convert both sides to an integer before doing the
-        -- comparision. Hence, the integer value 10 in t3 will compare equal
-        -- to the string value '10.0' in t4 because the t4 value will be
-        -- converted into an integer.
-        return test:execsql [[
-            SELECT * FROM t4 WHERE x IN (SELECT a FROM t3);
-        ]]
-    end, {
-        -- <subquery-2.5.2>
-        "10"
-        -- </subquery-2.5.2>
-    })
-
-test:do_test(
-    "subquery-2.5.3.1",
-    function()
-        -- The t4i index cannot be used to resolve the "x IN (...)" constraint
-        -- because the constraint has integer affinity but t4i has text affinity.
-        return test:execsql [[
-            CREATE INDEX t4i ON t4(x);
-            SELECT * FROM t4 WHERE x IN (SELECT a FROM t3);
-        ]]
-    end, {
-        -- <subquery-2.5.3.1>
-        "10"
-        -- </subquery-2.5.3.1>
-    })
-
--- Tarantool: no-rowid is implied for the table, so query plan contains
--- scan over t4i. Verified w/ vanilla sql. Comment this case
---do_test subquery-2.5.3.2 {
--- Verify that the t4i index was not used in the previous query
---  execsql {
---    EXPLAIN QUERY PLAN
---    SELECT * FROM t4 WHERE x IN (SELECT a FROM t3);
---  }
---} {~/t4i/}
-test:do_execsql_test(
-    "subquery-2.5.4",
-    [[
-        DROP TABLE t3;
-        DROP TABLE t4;
-    ]], {
-        -- <subquery-2.5.4>
-        
-        -- </subquery-2.5.4>
-    })
-
 --------------------------------------------------------------------
 -- The following test cases - subquery-3.* - test tickets that
 -- were raised during development of correlated subqueries.
diff --git a/test/sql-tap/tkt-3998683a16.test.lua b/test/sql-tap/tkt-3998683a16.test.lua
index 885dcf5cd..9bc310358 100755
--- a/test/sql-tap/tkt-3998683a16.test.lua
+++ b/test/sql-tap/tkt-3998683a16.test.lua
@@ -26,29 +26,17 @@ test:do_test(
     function()
         return test:execsql [[
             CREATE TABLE t1(x  INT primary key, y NUMBER);
-            INSERT INTO t1 VALUES(1, '1.0');
-            INSERT INTO t1 VALUES(2, '.125');
-            INSERT INTO t1 VALUES(3, '123.');
-            INSERT INTO t1 VALUES(4, '123.e+2');
-            INSERT INTO t1 VALUES(5, '.125e+3');
-            INSERT INTO t1 VALUES(6, '123e4');
-            INSERT INTO t1 VALUES(11, '  1.0');
-            INSERT INTO t1 VALUES(12, '  .125');
-            INSERT INTO t1 VALUES(13, '  123.');
-            INSERT INTO t1 VALUES(14, '  123.e+2');
-            INSERT INTO t1 VALUES(15, '  .125e+3');
-            INSERT INTO t1 VALUES(16, '  123e4');
-            INSERT INTO t1 VALUES(21, '1.0  ');
-            INSERT INTO t1 VALUES(22, '.125  ');
-            INSERT INTO t1 VALUES(23, '123.  ');
-            INSERT INTO t1 VALUES(24, '123.e+2  ');
-            INSERT INTO t1 VALUES(25, '.125e+3  ');
-            INSERT INTO t1 VALUES(26, '123e4  ');
+            INSERT INTO t1 VALUES(1, 1.0);
+            INSERT INTO t1 VALUES(2, .125);
+            INSERT INTO t1 VALUES(3, 123.);
+            INSERT INTO t1 VALUES(4, 123.e+2);
+            INSERT INTO t1 VALUES(5, .125e+3);
+            INSERT INTO t1 VALUES(6, 123e4);
             SELECT x FROM t1 WHERE typeof(y)=='number' ORDER BY x;
         ]]
     end, {
         -- <tkt-3998683a16.1>
-        1, 2, 3, 4, 5, 6, 11, 12, 13, 14, 15, 16, 21, 22, 23, 24, 25, 26
+        1, 2, 3, 4, 5, 6
         -- </tkt-3998683a16.1>
     })
 
diff --git a/test/sql-tap/tkt-54844eea3f.test.lua b/test/sql-tap/tkt-54844eea3f.test.lua
index d6cd56e52..89d0d1218 100755
--- a/test/sql-tap/tkt-54844eea3f.test.lua
+++ b/test/sql-tap/tkt-54844eea3f.test.lua
@@ -62,10 +62,10 @@ test:do_execsql_test(
     "1.2",
     [[
         CREATE TABLE t4(id INT primary key, a TEXT, b TEXT, c TEXT);
-        INSERT INTO t4 VALUES(1, 'a', 1, 'one');
-        INSERT INTO t4 VALUES(2, 'a', 2, 'two');
-        INSERT INTO t4 VALUES(3, 'b', 1, 'three');
-        INSERT INTO t4 VALUES(4, 'b', 2, 'four');
+        INSERT INTO t4 VALUES(1, 'a', '1', 'one');
+        INSERT INTO t4 VALUES(2, 'a', '2', 'two');
+        INSERT INTO t4 VALUES(3, 'b', '1', 'three');
+        INSERT INTO t4 VALUES(4, 'b', '2', 'four');
         SELECT ( 
           SELECT c FROM (
             SELECT a,b,c FROM t4 WHERE a=output.a ORDER BY b LIMIT 10 OFFSET 1
diff --git a/test/sql-tap/tkt-7bbfb7d442.test.lua b/test/sql-tap/tkt-7bbfb7d442.test.lua
index 535303771..bfddcd920 100755
--- a/test/sql-tap/tkt-7bbfb7d442.test.lua
+++ b/test/sql-tap/tkt-7bbfb7d442.test.lua
@@ -109,13 +109,13 @@ if (1 > 0)
                     T1.Variant AS Variant,
                     T1.ControlDate AS ControlDate,
                     1 AS ControlState,
-                    COALESCE(T2.DeliveredQty,0) AS DeliveredQty
+                    CAST(COALESCE(T2.DeliveredQty,0) AS STRING) AS DeliveredQty
                 FROM (
                     SELECT
                         NEW.InventoryControlId AS InventoryControlId,
                         II.SKU AS SKU,
                         II.Variant AS Variant,
-                        COALESCE(LastClosedIC.ControlDate,NEW.ControlDate) AS ControlDate
+                        CAST(COALESCE(LastClosedIC.ControlDate,NEW.ControlDate) AS STRING) AS ControlDate
                     FROM
                         InventoryItem II
                     LEFT JOIN
diff --git a/test/sql-tap/tkt-9a8b09f8e6.test.lua b/test/sql-tap/tkt-9a8b09f8e6.test.lua
index cb5348ab4..ca3a5427a 100755
--- a/test/sql-tap/tkt-9a8b09f8e6.test.lua
+++ b/test/sql-tap/tkt-9a8b09f8e6.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(49)
+test:plan(47)
 
 --!./tcltestrunner.lua
 -- 2014 June 26
@@ -193,16 +193,6 @@ test:do_execsql_test(
         -- </3.3>
     })
 
-test:do_execsql_test(
-    3.4,
-    [[
-        SELECT x FROM t2 WHERE x IN ('1');
-    ]], {
-        -- <3.4>
-        1
-        -- </3.4>
-    })
-
 test:do_execsql_test(
     3.5,
     [[
@@ -233,16 +223,6 @@ test:do_execsql_test(
         -- </3.7>
     })
 
-test:do_execsql_test(
-    3.8,
-    [[
-        SELECT x FROM t2 WHERE '1' IN (x);
-    ]], {
-        -- <3.8>
-        1
-        -- </3.8>
-    })
-
 test:do_execsql_test(
     4.1,
     [[
@@ -263,23 +243,23 @@ test:do_execsql_test(
         -- </4.2>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     4.3,
     [[
         SELECT x FROM t3 WHERE x IN ('1');
     ]], {
         -- <4.3>
-        1.0
+        1, "Type mismatch: can not convert 1 to number"
         -- </4.3>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     4.4,
     [[
         SELECT x FROM t3 WHERE x IN ('1.0');
     ]], {
         -- <4.4>
-        1.0
+        1, "Type mismatch: can not convert 1.0 to number"
         -- </4.4>
     })
 
@@ -303,23 +283,23 @@ test:do_execsql_test(
         -- </4.6>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     4.7,
     [[
         SELECT x FROM t3 WHERE '1' IN (x);
     ]], {
         -- <4.7>
-        1
+        1, "Type mismatch: can not convert 1 to number"
         -- </4.7>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     4.8,
     [[
         SELECT x FROM t3 WHERE '1.0' IN (x);
     ]], {
         -- <4.8>
-        1
+        1, "Type mismatch: can not convert 1.0 to number"
         -- </4.8>
     })
 
@@ -343,23 +323,23 @@ test:do_execsql_test(
         -- </5.2>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     5.3,
     [[
         SELECT x FROM t4 WHERE x IN ('1');
     ]], {
         -- <5.3>
-        
+        1, "Type mismatch: can not convert 1 to number"
         -- </5.3>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     5.4,
     [[
         SELECT x FROM t4 WHERE x IN ('1.0');
     ]], {
         -- <5.4>
-        
+        1, "Type mismatch: can not convert 1.0 to number"
         -- </5.4>
     })
 
@@ -373,13 +353,13 @@ test:do_execsql_test(
         -- </5.5>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     5.6,
     [[
         SELECT x FROM t4 WHERE x IN ('1.11');
     ]], {
         -- <5.6>
-        1.11
+        1, "Type mismatch: can not convert 1.11 to number"
         -- </5.6>
     })
 
@@ -403,23 +383,23 @@ test:do_execsql_test(
         -- </5.8>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     5.9,
     [[
         SELECT x FROM t4 WHERE '1' IN (x);
     ]], {
         -- <5.9>
-        
+        1, "Type mismatch: can not convert 1 to number"
         -- </5.9>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     5.10,
     [[
         SELECT x FROM t4 WHERE '1.0' IN (x);
     ]], {
         -- <5.10>
-        
+        1, "Type mismatch: can not convert 1.0 to number"
         -- </5.10>
     })
 
@@ -433,13 +413,13 @@ test:do_execsql_test(
         -- </5.11>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     5.12,
     [[
         SELECT x FROM t4 WHERE '1.11' IN (x);
     ]], {
         -- <5.12>
-        1.11
+        1, "Type mismatch: can not convert 1.11 to number"
         -- </5.12>
     })
 
diff --git a/test/sql-tap/tkt-f973c7ac31.test.lua b/test/sql-tap/tkt-f973c7ac31.test.lua
index 82bdb52f8..381f29c65 100755
--- a/test/sql-tap/tkt-f973c7ac31.test.lua
+++ b/test/sql-tap/tkt-f973c7ac31.test.lua
@@ -39,9 +39,8 @@ for tn, sql in ipairs(sqls) do
     test:do_execsql_test(
         "tkt-f973c7ac3-1."..tn..".1",
         [[
-            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>0 AND c2<='2' ORDER BY c2 DESC 
+            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>0 AND CAST(c2 AS STRING)<='2' ORDER BY c2 DESC
         ]], {
-            
         })
 
     test:do_execsql_test(
@@ -55,7 +54,7 @@ for tn, sql in ipairs(sqls) do
     test:do_execsql_test(
         "tkt-f973c7ac3-1."..tn..".3",
         [[
-            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>0 AND c2<='5' ORDER BY c2 DESC 
+            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>0 AND CAST(c2 AS STRING)<='5' ORDER BY c2 DESC
         ]], {
             5, 5, 5, 4
         })
@@ -63,7 +62,7 @@ for tn, sql in ipairs(sqls) do
     test:do_execsql_test(
         "tkt-f973c7ac3-1."..tn..".4",
         [[
-            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>'0' AND c2<=5 ORDER BY c2 DESC 
+            SELECT c1,c2 FROM t WHERE c1 = 5 AND CAST(c2 AS STRING)>'0' AND c2<=5 ORDER BY c2 DESC
         ]], {
             5, 5, 5, 4
         })
@@ -71,7 +70,7 @@ for tn, sql in ipairs(sqls) do
     test:do_execsql_test(
         "tkt-f973c7ac3-1."..tn..".5",
         [[
-            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>'0' AND c2<='5' ORDER BY c2 DESC 
+            SELECT c1,c2 FROM t WHERE c1 = 5 AND CAST(c2 AS STRING)>'0' AND CAST(c2 AS STRING)<='5' ORDER BY c2 DESC
         ]], {
             5, 5, 5, 4
         })
@@ -79,9 +78,8 @@ for tn, sql in ipairs(sqls) do
     test:do_execsql_test(
         "tkt-f973c7ac3-1."..tn..".6",
         [[
-            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>0 AND c2<='2' ORDER BY c2 ASC 
+            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>0 AND CAST(c2 AS STRING)<='2' ORDER BY c2 ASC
         ]], {
-            
         })
 
     test:do_execsql_test(
@@ -95,7 +93,7 @@ for tn, sql in ipairs(sqls) do
     test:do_execsql_test(
         "tkt-f973c7ac3-1."..tn..".8",
         [[
-            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>0 AND c2<='5' ORDER BY c2 ASC 
+            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>0 AND CAST(c2 AS STRING)<='5' ORDER BY c2 ASC
         ]], {
             5, 4, 5, 5
         })
@@ -103,7 +101,7 @@ for tn, sql in ipairs(sqls) do
     test:do_execsql_test(
         "tkt-f973c7ac3-1."..tn..".9",
         [[
-            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>'0' AND c2<=5 ORDER BY c2 ASC 
+            SELECT c1,c2 FROM t WHERE c1 = 5 AND CAST(c2 AS STRING)>'0' AND c2<=5 ORDER BY c2 ASC
         ]], {
             5, 4, 5, 5
         })
@@ -111,7 +109,7 @@ for tn, sql in ipairs(sqls) do
     test:do_execsql_test(
         "tkt-f973c7ac3-1."..tn..".10",
         [[
-            SELECT c1,c2 FROM t WHERE c1 = 5 AND c2>'0' AND c2<='5' ORDER BY c2 ASC 
+            SELECT c1,c2 FROM t WHERE c1 = 5 AND CAST(c2 AS STRING)>'0' AND CAST(c2 AS STRING)<='5' ORDER BY c2 ASC
         ]], {
             5, 4, 5, 5
         })
diff --git a/test/sql-tap/tkt-fc7bd6358f.test.lua b/test/sql-tap/tkt-fc7bd6358f.test.lua
deleted file mode 100755
index fe5d6200f..000000000
--- a/test/sql-tap/tkt-fc7bd6358f.test.lua
+++ /dev/null
@@ -1,111 +0,0 @@
-#!/usr/bin/env tarantool
-test = require("sqltester")
-test:plan(50)
-
---!./tcltestrunner.lua
--- 2013 March 05
---
--- The author disclaims copyright to this source code.  In place of
--- a legal notice, here is a blessing:
---
---    May you do good and not evil.
---    May you find forgiveness for yourself and forgive others.
---    May you share freely, never taking more than you give.
---
--------------------------------------------------------------------------
--- This file implements regression tests for sql library. Specifically,
--- it tests that ticket [fc7bd6358f]:
---
--- The following SQL yields an incorrect result (zero rows) in all
--- versions of sql between 3.6.14 and 3.7.15.2:
---
---    CREATE TABLE t(textid TEXT);
---    INSERT INTO t VALUES('12');
---    INSERT INTO t VALUES('34');
---    CREATE TABLE i(intid INTEGER PRIMARY KEY);
---    INSERT INTO i VALUES(12);
---    INSERT INTO i VALUES(34);
---
---    SELECT t1.textid AS a, i.intid AS b, t2.textid AS c
---      FROM t t1, i, t t2
---     WHERE t1.textid = i.intid
---       AND t1.textid = t2.textid;
---
--- The correct result should be two rows, one with 12|12|12 and the other
--- with 34|34|34. With this bug, no rows are returned. Bisecting shows that
--- this bug was introduced with check-in [dd4d67a67454] on 2009-04-23. 
---
--- ["set","testdir",[["file","dirname",["argv0"]]]]
--- ["source",[["testdir"],"\/tester.tcl"]]
-test:do_test(
-    "tkt-fc7bd6358f.100",
-    function()
-        return test:execsql [[
-            CREATE TABLE t(textid TEXT PRIMARY KEY);
-            INSERT INTO t VALUES('12');
-            INSERT INTO t VALUES('34');
-            CREATE TABLE i(intid INTEGER PRIMARY KEY);
-            INSERT INTO i VALUES(12);
-            INSERT INTO i VALUES(34);
-        ]]
-    end, {
-        -- <tkt-fc7bd6358f.100>
-        
-        -- </tkt-fc7bd6358f.100>
-    })
-
--- ["unset","-nocomplain","from"]
--- ["unset","-nocomplain","where"]
--- ["unset","-nocomplain","a"]
--- ["unset","-nocomplain","b"]
-local froms = {
-    "FROM t t1, i, t t2",
-    "FROM i, t t1, t t2",
-    "FROM t t1, t t2, i",
-}
-local wheres = {
-    "WHERE t1.textid=i.intid AND t1.textid=t2.textid",
-    "WHERE i.intid=t1.textid AND t1.textid=t2.textid",
-    "WHERE t1.textid=i.intid AND i.intid=t2.textid",
-    "WHERE t1.textid=i.intid AND t2.textid=i.intid",
-    "WHERE i.intid=t1.textid AND i.intid=t2.textid",
-    "WHERE i.intid=t1.textid AND t2.textid=i.intid",
-    "WHERE t1.textid=t2.textid AND i.intid=t2.textid",
-    "WHERE t1.textid=t2.textid AND t2.textid=i.intid",
-}
-for a, from in ipairs(froms) do
-    for b, where in ipairs(wheres) do
-        test:do_test(
-            string.format("tkt-fc7bd6358f.110.%s.%s.1", a, b),
-            function()
-                return test:execsql(string.format("SELECT t1.textid, i.intid, t2.textid %s %s", from, where))
-            end, {
-                "12", 12, "12", "34", 34, "34"
-            })
-
-        test:do_test(
-            string.format("tkt-fc7bd6358f.110.%s.%s.2", a, b),
-            function()
-                return test:execsql(string.format("SELECT t1.textid, i.intid, t2.textid %s %s", from, where))
-            end, {
-                "12", 12, "12", "34", 34, "34"
-            })
-
-    end
-end
-
-test:do_test(
-    "tkt-fc7bd6358f.200",
-    function()
-        return test:execsql [[
-            DROP TABLE t;
-            DROP TABLE i;
-        ]]
-    end, {
-        -- <tkt-fc7bd6358f.100>
-        
-        -- </tkt-fc7bd6358f.100>
-    })
-
-test:finish_test()
-
diff --git a/test/sql-tap/tkt1444.test.lua b/test/sql-tap/tkt1444.test.lua
index 82a5ded25..fb148bc5f 100755
--- a/test/sql-tap/tkt1444.test.lua
+++ b/test/sql-tap/tkt1444.test.lua
@@ -30,8 +30,8 @@ test:do_execsql_test(
     [[
         CREATE TABLE DemoTable (id  INT primary key, x INTEGER, TextKey TEXT, DKey NUMBER);
         CREATE INDEX DemoTableIdx ON DemoTable (TextKey);
-        INSERT INTO DemoTable VALUES(1, 9,8,7);
-        INSERT INTO DemoTable VALUES(2, 1,2,3);
+        INSERT INTO DemoTable VALUES(1, 9,'8',7);
+        INSERT INTO DemoTable VALUES(2, 1,'2',3);
         CREATE VIEW DemoView AS SELECT x, TextKey, DKey FROM DemoTable ORDER BY TextKey;
         SELECT x,TextKey,DKey FROM DemoTable UNION ALL SELECT * FROM DemoView ORDER BY 1;
     ]], {
diff --git a/test/sql-tap/tkt3493.test.lua b/test/sql-tap/tkt3493.test.lua
index 7ceec4702..de77e61e9 100755
--- a/test/sql-tap/tkt3493.test.lua
+++ b/test/sql-tap/tkt3493.test.lua
@@ -29,8 +29,8 @@ test:do_execsql_test(
         START TRANSACTION;
         INSERT INTO A VALUES(1,'123');
         INSERT INTO A VALUES(2,'456');
-        INSERT INTO B VALUES(1,1);
-        INSERT INTO B VALUES(2,2);
+        INSERT INTO B VALUES(1,'1');
+        INSERT INTO B VALUES(2,'2');
         INSERT INTO A_B VALUES(1,1);
         INSERT INTO A_B VALUES(2,2);
         COMMIT;
@@ -116,7 +116,7 @@ test:do_execsql_test(
     "tkt3493-2.1",
     [[
         CREATE TABLE t1(a TEXT PRIMARY KEY, b INT);
-        INSERT INTO t1 VALUES(123, 456);
+        INSERT INTO t1 VALUES('123', 456);
     ]], {
         -- <tkt3493-2.1>
         
diff --git a/test/sql-tap/tkt3841.test.lua b/test/sql-tap/tkt3841.test.lua
index 5203d0cd4..56668f6a3 100755
--- a/test/sql-tap/tkt3841.test.lua
+++ b/test/sql-tap/tkt3841.test.lua
@@ -31,12 +31,12 @@ test:do_execsql_test(
 
         INSERT INTO table2 VALUES ('a', 'alist');
         INSERT INTO table2 VALUES ('b', 'blist');
-        INSERT INTO list VALUES ('a', 1);
-        INSERT INTO list VALUES ('a', 2);
-        INSERT INTO list VALUES ('a', 3);
-        INSERT INTO list VALUES ('b', 4);
-        INSERT INTO list VALUES ('b', 5);
-        INSERT INTO list VALUES ('b', 6);
+        INSERT INTO list VALUES ('a', '1');
+        INSERT INTO list VALUES ('a', '2');
+        INSERT INTO list VALUES ('a', '3');
+        INSERT INTO list VALUES ('b', '4');
+        INSERT INTO list VALUES ('b', '5');
+        INSERT INTO list VALUES ('b', '6');
 
         SELECT
           table2.x,
diff --git a/test/sql-tap/transitive1.test.lua b/test/sql-tap/transitive1.test.lua
index e96056580..96895b4a7 100755
--- a/test/sql-tap/transitive1.test.lua
+++ b/test/sql-tap/transitive1.test.lua
@@ -338,7 +338,7 @@ test:do_execsql_test(
                    ON tvshow.idshow = episode.idshow
                  LEFT JOIN seasons
                         ON seasons.idshow = episode.idshow
-                           AND seasons.season = episode.c12
+                           AND seasons.season = CAST(episode.c12 AS INTEGER)
                  JOIN path
                    ON files.idpath = path.idpath
                  LEFT JOIN bookmark
@@ -378,7 +378,7 @@ test:do_execsql_test(
         FROM episodeview
             JOIN tvshowview ON tvshowview.idShow = episodeview.idShow
             JOIN seasons ON (seasons.idShow = tvshowview.idShow
-                             AND seasons.season = episodeview.c12)
+                             AND seasons.season = CAST(episodeview.c12 AS INTEGER))
             JOIN files ON files.idFile = episodeview.idFile
             JOIN tvshowlinkpath ON tvshowlinkpath.idShow = tvshowview.idShow
             JOIN path ON path.idPath = tvshowlinkpath.idPath
diff --git a/test/sql-tap/triggerA.test.lua b/test/sql-tap/triggerA.test.lua
index fac51ca14..fc8ecfe17 100755
--- a/test/sql-tap/triggerA.test.lua
+++ b/test/sql-tap/triggerA.test.lua
@@ -283,7 +283,7 @@ test:do_test(
             CREATE TABLE result2(id INTEGER PRIMARY KEY, a TEXT,b INT);
             CREATE TRIGGER r5d INSTEAD OF DELETE ON v5 FOR EACH ROW BEGIN
               INSERT INTO result2(id, a,b) VALUES((SELECT coalesce(max(id),0) + 1 FROM result2),
-                                                  old.x, old.b);
+                                                  CAST(old.x AS STRING), old.b);
             END;
             DELETE FROM v5 WHERE x=5;
             SELECT a, b FROM result2;
@@ -301,7 +301,7 @@ test:do_test(
             DELETE FROM result4;
             CREATE TRIGGER r5u INSTEAD OF UPDATE ON v5 FOR EACH ROW BEGIN
               INSERT INTO result4(id, a,b,c,d) VALUES((SELECT coalesce(max(id),0) + 1 FROM result4),
-                                                      old.x, old.b, new.x, new.b);
+                                                      CAST(old.x AS STRING), old.b, CAST(new.x AS STRING), new.b);
             END;
             UPDATE v5 SET b = b+9900000 WHERE x BETWEEN 3 AND 5;
             SELECT a,b,c,d FROM result4 ORDER BY a;
diff --git a/test/sql-tap/unique.test.lua b/test/sql-tap/unique.test.lua
index 9818f90a8..6b0a7e20d 100755
--- a/test/sql-tap/unique.test.lua
+++ b/test/sql-tap/unique.test.lua
@@ -52,7 +52,7 @@ test:do_catchsql_test(
 test:do_catchsql_test(
     "unique-1.2",
     [[
-        INSERT INTO t1(a,b,c) VALUES(1,2,3)
+        INSERT INTO t1(a,b,c) VALUES(1,2,'3')
     ]], {
         -- <unique-1.2>
         0
@@ -62,7 +62,7 @@ test:do_catchsql_test(
 test:do_catchsql_test(
     "unique-1.3",
     [[
-        INSERT INTO t1(a,b,c) VALUES(1,3,4)
+        INSERT INTO t1(a,b,c) VALUES(1,3,'4')
     ]], {
         -- <unique-1.3>
         1, "Duplicate key exists in unique index 'pk_unnamed_T1_1' in space 'T1'"
@@ -83,7 +83,7 @@ test:do_execsql_test(
 test:do_catchsql_test(
     "unique-1.5",
     [[
-        INSERT INTO t1(a,b,c) VALUES(3,2,4)
+        INSERT INTO t1(a,b,c) VALUES(3,2,'4')
     ]], {
         -- <unique-1.5>
         1, "Duplicate key exists in unique index 'unique_unnamed_T1_2' in space 'T1'"
@@ -104,7 +104,7 @@ test:do_execsql_test(
 test:do_catchsql_test(
     "unique-1.7",
     [[
-        INSERT INTO t1(a,b,c) VALUES(3,4,5)
+        INSERT INTO t1(a,b,c) VALUES(3,4,'5')
     ]], {
         -- <unique-1.7>
         0
diff --git a/test/sql-tap/view.test.lua b/test/sql-tap/view.test.lua
index e553b91c7..ab14c5edb 100755
--- a/test/sql-tap/view.test.lua
+++ b/test/sql-tap/view.test.lua
@@ -757,7 +757,7 @@ test:do_execsql_test(
     "view-10.1",
     [=[
         CREATE TABLE t3("9" integer primary key, "4" text);
-        INSERT INTO t3 VALUES(1,2);
+        INSERT INTO t3 VALUES(1,'2');
         CREATE VIEW v_t3_a AS SELECT a."9" FROM t3 AS a;
         CREATE VIEW v_t3_b AS SELECT "4" FROM t3;
         SELECT * FROM v_t3_a;
diff --git a/test/sql-tap/where5.test.lua b/test/sql-tap/where5.test.lua
index 749201564..3aefcaca5 100755
--- a/test/sql-tap/where5.test.lua
+++ b/test/sql-tap/where5.test.lua
@@ -27,11 +27,11 @@ test:do_test("where5-1.0", function()
         CREATE TABLE t1(x TEXT primary key);
         CREATE TABLE t2(x integer primary key);
         CREATE TABLE t3(x integer PRIMARY KEY);
-        INSERT INTO t1 VALUES(-1);
-        INSERT INTO t1 VALUES(0);
-        INSERT INTO t1 VALUES(1);
-        INSERT INTO t2 SELECT * FROM t1;
-        INSERT INTO t3 SELECT * FROM t1;
+        INSERT INTO t1 VALUES('-1');
+        INSERT INTO t1 VALUES('0');
+        INSERT INTO t1 VALUES('1');
+        INSERT INTO t2 SELECT CAST(x AS INTEGER) FROM t1;
+        INSERT INTO t3 SELECT CAST(x AS INTEGER) FROM t1;
     ]]
     return test:execsql [[
         SELECT * FROM t1 WHERE x<0
diff --git a/test/sql-tap/whereB.test.lua b/test/sql-tap/whereB.test.lua
deleted file mode 100755
index d98645fdc..000000000
--- a/test/sql-tap/whereB.test.lua
+++ /dev/null
@@ -1,908 +0,0 @@
-#!/usr/bin/env tarantool
-test = require("sqltester")
-test:plan(63)
-
---!./tcltestrunner.lua
--- 2009 August 13
---
--- The author disclaims copyright to this source code.  In place of
--- a legal notice, here is a blessing:
---
---    May you do good and not evil.
---    May you find forgiveness for yourself and forgive others.
---    May you share freely, never taking more than you give.
---
--------------------------------------------------------------------------
--- This file implements regression tests for sql library. The
--- focus of this file is testing WHERE clause conditions with
--- subtle affinity issues.
---
--- ["set","testdir",[["file","dirname",["argv0"]]]]
--- ["source",[["testdir"],"\/tester.tcl"]]
--- For this set of tests:
---
---  *   t1.y holds an integer value with affinity NONE
---  *   t2.b holds a text value with affinity TEXT
---
--- These values are not equal and because neither affinity is NUMERIC
--- no type conversion occurs.
---
-test:do_execsql_test(
-    "whereB-1.1",
-    [[
-        CREATE TABLE t1(x  INT primary key,y INT );    -- affinity of t1.y is NONE
-        INSERT INTO t1 VALUES(1,99);
-
-        CREATE TABLE t2(a  INT primary key, b TEXT);  -- affinity of t2.b is TEXT
-        CREATE INDEX t2b ON t2(b);
-        INSERT INTO t2 VALUES(2,'99');
-
-        SELECT x, a, y=b FROM t1, t2 ORDER BY +x, +a;
-    ]],
-    {
-    -- <whereB-1.1>
-    1, 2, true
-    -- </whereB-1.1>
-    })
-
-test:do_execsql_test(
-    "whereB-1.2",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-1.2>
-    1, 2, true
-    -- </whereB-1.2>
-    })
-
-test:do_execsql_test(
-    "whereB-1.3",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-1.3>
-    1, 2, true
-    -- </whereB-1.3>
-    })
-
-test:do_execsql_test(
-    "whereB-1.4",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-1.4>
-    1, 2, true
-    -- </whereB-1.4>
-    })
-
-test:do_execsql_test(
-    "whereB-1.100",
-    [[
-        DROP INDEX t2b ON t2;
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-1.100>
-    1, 2, true
-    -- </whereB-1.100>
-    })
-
-test:do_execsql_test(
-    "whereB-1.101",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-1.101>
-    1, 2, true
-    -- </whereB-1.101>
-    })
-
-test:do_execsql_test(
-    "whereB-1.102",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-1.102>
-    1, 2, true
-    -- </whereB-1.102>
-    })
-
--- For this set of tests:
---
---  *   t1.y holds a text value with affinity TEXT
---  *   t2.b holds an integer value with affinity NONE
---
--- These values are not equal and because neither affinity is NUMERIC
--- no type conversion occurs.
---
-test:do_execsql_test(
-    "whereB-2.1",
-    [[
-        DROP TABLE t1;
-        DROP TABLE t2;
-
-        CREATE TABLE t1(x  INT primary key, y TEXT);    -- affinity of t1.y is TEXT
-        INSERT INTO t1 VALUES(1,99);
-
-        CREATE TABLE t2(a  INT primary key, b SCALAR);  -- affinity of t2.b is NONE
-        CREATE INDEX t2b ON t2(b);
-        INSERT INTO t2 VALUES(2, 99);
-
-        SELECT x, a, y=b FROM t1, t2 ORDER BY +x, +a;
-    ]],
-    {
-    -- <whereB-2.1>
-    1, 2, false
-    -- </whereB-2.1>
-    })
-
-test:do_execsql_test(
-    "whereB-2.2",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-2.2>
-    
-    -- </whereB-2.2>
-    })
-
-test:do_execsql_test(
-    "whereB-2.3",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-2.3>
-    
-    -- </whereB-2.3>
-    })
-
-test:do_execsql_test(
-    "whereB-2.4",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-2.4>
-    
-    -- </whereB-2.4>
-    })
-
-test:do_execsql_test(
-    "whereB-2.100",
-    [[
-        DROP INDEX t2b ON t2;
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-2.100>
-    
-    -- </whereB-2.100>
-    })
-
-test:do_execsql_test(
-    "whereB-2.101",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-2.101>
-    
-    -- </whereB-2.101>
-    })
-
-test:do_execsql_test(
-    "whereB-2.102",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-2.102>
-    
-    -- </whereB-2.102>
-    })
-
--- For this set of tests:
---
---  *   t1.y holds a text value with affinity NONE
---  *   t2.b holds an integer value with affinity NONE
---
--- These values are not equal and because neither affinity is NUMERIC
--- no type conversion occurs.
---
-test:do_execsql_test(
-    "whereB-3.1",
-    [[
-        DROP TABLE t1;
-        DROP TABLE t2;
-
-        CREATE TABLE t1(x  INT primary key, y SCALAR);    -- affinity of t1.y is NONE
-        INSERT INTO t1 VALUES(1,99);
-
-        CREATE TABLE t2(a  INT primary key, b SCALAR);  -- affinity of t2.b is NONE
-        CREATE INDEX t2b ON t2(b);
-        INSERT INTO t2 VALUES(2,'99');
-
-        SELECT x, a, y=b FROM t1, t2;
-    ]],
-    {
-    -- <whereB-3.1>
-    1, 2, false
-    -- </whereB-3.1>
-    })
-
-test:do_execsql_test(
-    "whereB-3.2",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-3.2>
-    
-    -- </whereB-3.2>
-    })
-
-test:do_execsql_test(
-    "whereB-3.3",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-3.3>
-    
-    -- </whereB-3.3>
-    })
-
-test:do_execsql_test(
-    "whereB-3.4",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-3.4>
-    
-    -- </whereB-3.4>
-    })
-
-test:do_execsql_test(
-    "whereB-3.100",
-    [[
-        DROP INDEX t2b ON t2;
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-3.100>
-    
-    -- </whereB-3.100>
-    })
-
-test:do_execsql_test(
-    "whereB-3.101",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-3.101>
-    
-    -- </whereB-3.101>
-    })
-
-test:do_execsql_test(
-    "whereB-3.102",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-3.102>
-    
-    -- </whereB-3.102>
-    })
-
--- For this set of tests:
---
---  *   t1.y holds a text value with affinity NONE
---  *   t2.b holds an integer value with affinity NUMERIC
---
--- Because t2.b has a numeric affinity, type conversion should occur
--- and the two fields should be equal.
---
-test:do_execsql_test(
-    "whereB-4.1",
-    [[
-        DROP TABLE IF EXISTS t1;
-        DROP TABLE IF EXISTS t2;
-
-        CREATE TABLE t1(x  INT primary key, y SCALAR);    -- affinity of t1.y is NONE
-        INSERT INTO t1 VALUES(1,'99');
-
-        CREATE TABLE t2(a  INT primary key, b NUMBER);  -- affinity of t2.b is NUMERIC
-        CREATE INDEX t2b ON t2(b);
-        INSERT INTO t2 VALUES(2,99);
-
-        SELECT x, a, y=b FROM t1, t2;
-    ]],
-    {
-    -- <whereB-4.1>
-    1, 2, true
-    -- </whereB-4.1>
-    })
-
-test:do_execsql_test(
-    "whereB-4.2",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-4.2>
-    1, 2, true
-    -- </whereB-4.2>
-    })
-
-test:do_execsql_test(
-    "whereB-4.3",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-4.3>
-    1, 2, true
-    -- </whereB-4.3>
-    })
-
-test:do_execsql_test(
-    "whereB-4.4",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-4.4>
-    1, 2, true
-    -- </whereB-4.4>
-    })
-
-test:do_execsql_test(
-    "whereB-4.100",
-    [[
-        DROP INDEX t2b ON t2;
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-4.100>
-    1, 2, true
-    -- </whereB-4.100>
-    })
-
-test:do_execsql_test(
-    "whereB-4.101",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-4.101>
-    1, 2, true
-    -- </whereB-4.101>
-    })
-
-test:do_execsql_test(
-    "whereB-4.102",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-4.102>
-    1, 2, true
-    -- </whereB-4.102>
-    })
-
--- For this set of tests:
---
---  *   t1.y holds a text value with affinity NONE
---  *   t2.b holds an integer value with affinity INTEGER
---
--- Because t2.b has a numeric affinity, type conversion should occur
--- and the two fields should be equal.
---
-test:do_execsql_test(
-    "whereB-5.1",
-    [[
-        DROP TABLE t1;
-        DROP TABLE t2;
-
-        CREATE TABLE t1(x  INT primary key, y SCALAR);    -- affinity of t1.y is NONE
-        INSERT INTO t1 VALUES(1,'99');
-
-        CREATE TABLE t2(a  INT primary key, b INT);  -- affinity of t2.b is INTEGER
-        CREATE INDEX t2b ON t2(b);
-        INSERT INTO t2 VALUES(2,99);
-
-        SELECT x, a, y=b FROM t1, t2;
-    ]],
-    {
-    -- <whereB-5.1>
-    1, 2, true
-    -- </whereB-5.1>
-    })
-
-test:do_execsql_test(
-    "whereB-5.2",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-5.2>
-    1, 2, true
-    -- </whereB-5.2>
-    })
-
-test:do_execsql_test(
-    "whereB-5.3",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-5.3>
-    1, 2, true
-    -- </whereB-5.3>
-    })
-
-test:do_execsql_test(
-    "whereB-5.4",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-5.4>
-    1, 2, true
-    -- </whereB-5.4>
-    })
-
-test:do_execsql_test(
-    "whereB-5.100",
-    [[
-        DROP INDEX t2b ON t2;
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-5.100>
-    1, 2, true
-    -- </whereB-5.100>
-    })
-
-test:do_execsql_test(
-    "whereB-5.101",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-5.101>
-    1, 2, true
-    -- </whereB-5.101>
-    })
-
-test:do_execsql_test(
-    "whereB-5.102",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-5.102>
-    1, 2, true
-    -- </whereB-5.102>
-    })
-
--- For this set of tests:
---
---  *   t1.y holds a text value with affinity NONE
---  *   t2.b holds an integer value with affinity REAL
---
--- Because t2.b has a numeric affinity, type conversion should occur
--- and the two fields should be equal.
---
-test:do_execsql_test(
-    "whereB-6.1",
-    [[
-        DROP TABLE t1;
-        DROP TABLE t2;
-
-        CREATE TABLE t1(x  INT primary key, y SCALAR);    -- affinity of t1.y is NONE
-        INSERT INTO t1 VALUES(1,'99');
-
-        CREATE TABLE t2(a  INT primary key, b NUMBER);  -- affinity of t2.b is REAL
-        CREATE INDEX t2b ON t2(b);
-        INSERT INTO t2 VALUES(2,99.0);
-
-        SELECT x, a, y=b FROM t1, t2;
-    ]],
-    {
-    -- <whereB-6.1>
-    1, 2, true
-    -- </whereB-6.1>
-    })
-
-test:do_execsql_test(
-    "whereB-6.2",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-6.2>
-    1, 2, true
-    -- </whereB-6.2>
-    })
-
-test:do_execsql_test(
-    "whereB-6.3",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-6.3>
-    1, 2, true
-    -- </whereB-6.3>
-    })
-
-test:do_execsql_test(
-    "whereB-6.4",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-6.4>
-    1, 2, true
-    -- </whereB-6.4>
-    })
-
-test:do_execsql_test(
-    "whereB-6.100",
-    [[
-        DROP INDEX t2b ON t2;
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-6.100>
-    1, 2, true
-    -- </whereB-6.100>
-    })
-
-test:do_execsql_test(
-    "whereB-6.101",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-6.101>
-    1, 2, true
-    -- </whereB-6.101>
-    })
-
-test:do_execsql_test(
-    "whereB-6.102",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-6.102>
-    1, 2, true
-    -- </whereB-6.102>
-    })
-
--- For this set of tests:
---
---  *   t1.y holds an integer value with affinity NUMERIC
---  *   t2.b holds a text value with affinity NONE
---
--- Because t1.y has a numeric affinity, type conversion should occur
--- and the two fields should be equal.
---
-test:do_execsql_test(
-    "whereB-7.1",
-    [[
-        DROP TABLE t1;
-        DROP TABLE t2;
-
-        CREATE TABLE t1(x  INT primary key, y NUMBER);  -- affinity of t1.y is NUMERIC
-        INSERT INTO t1 VALUES(1,99);
-
-        CREATE TABLE t2(a  INT primary key, b SCALAR);  -- affinity of t2.b is NONE
-        CREATE INDEX t2b ON t2(b);
-        INSERT INTO t2 VALUES(2,'99');
-
-        SELECT x, a, y=b FROM t1, t2;
-    ]],
-    {
-    -- <whereB-7.1>
-    1, 2, true
-    -- </whereB-7.1>
-    })
-
-test:do_execsql_test(
-    "whereB-7.2",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-7.2>
-    1, 2, true
-    -- </whereB-7.2>
-    })
-
-test:do_execsql_test(
-    "whereB-7.3",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-7.3>
-    1, 2, true
-    -- </whereB-7.3>
-    })
-
-test:do_execsql_test(
-    "whereB-7.4",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-7.4>
-    1, 2, true
-    -- </whereB-7.4>
-    })
-
-test:do_execsql_test(
-    "whereB-7.100",
-    [[
-        DROP INDEX t2b ON t2;
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-7.100>
-    1, 2, true
-    -- </whereB-7.100>
-    })
-
-test:do_execsql_test(
-    "whereB-7.101",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-7.101>
-    1, 2, true
-    -- </whereB-7.101>
-    })
-
-test:do_execsql_test(
-    "whereB-7.102",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-7.102>
-    1, 2, true
-    -- </whereB-7.102>
-    })
-
--- For this set of tests:
---
---  *   t1.y holds an integer value with affinity INTEGER
---  *   t2.b holds a text value with affinity NONE
---
--- Because t1.y has a numeric affinity, type conversion should occur
--- and the two fields should be equal.
---
-test:do_execsql_test(
-    "whereB-8.1",
-    [[
-        DROP TABLE t1;
-        DROP TABLE t2;
-
-        CREATE TABLE t1(x  INT primary key, y INT);  -- affinity of t1.y is INTEGER
-        INSERT INTO t1 VALUES(1,99);
-
-        CREATE TABLE t2(a  INT primary key, b SCALAR);  -- affinity of t2.b is NONE
-        CREATE INDEX t2b ON t2(b);
-        INSERT INTO t2 VALUES(2,'99');
-
-        SELECT x, a, y=b FROM t1, t2;
-    ]],
-    {
-    -- <whereB-8.1>
-    1, 2, true
-    -- </whereB-8.1>
-    })
-
-test:do_execsql_test(
-    "whereB-8.2",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-8.2>
-    1, 2, true
-    -- </whereB-8.2>
-    })
-
-test:do_execsql_test(
-    "whereB-8.3",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-8.3>
-    1, 2, true
-    -- </whereB-8.3>
-    })
-
-test:do_execsql_test(
-    "whereB-8.4",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-8.4>
-    1, 2, true
-    -- </whereB-8.4>
-    })
-
-test:do_execsql_test(
-    "whereB-8.100",
-    [[
-        DROP INDEX t2b ON t2;
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-8.100>
-    1, 2, true
-    -- </whereB-8.100>
-    })
-
-test:do_execsql_test(
-    "whereB-8.101",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-8.101>
-    1, 2, true
-    -- </whereB-8.101>
-    })
-
-test:do_execsql_test(
-    "whereB-8.102",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-8.102>
-    1, 2, true
-    -- </whereB-8.102>
-    })
-
--- For this set of tests:
---
---  *   t1.y holds an integer value with affinity REAL
---  *   t2.b holds a text value with affinity NONE
---
--- Because t1.y has a numeric affinity, type conversion should occur
--- and the two fields should be equal.
---
-test:do_execsql_test(
-    "whereB-9.1",
-    [[
-        DROP TABLE t1;
-        DROP TABLE t2;
-
-        CREATE TABLE t1(x  INT primary key, y NUMBER);  -- affinity of t1.y is REAL
-        INSERT INTO t1 VALUES(1,99.0);
-
-        CREATE TABLE t2(a  INT primary key, b SCALAR);  -- affinity of t2.b is NONE
-        CREATE INDEX t2b ON t2(b);
-        INSERT INTO t2 VALUES(2,'99');
-
-        SELECT x, a, y=b FROM t1, t2;
-    ]],
-    {
-    -- <whereB-9.1>
-    1, 2, true
-    -- </whereB-9.1>
-    })
-
-test:do_execsql_test(
-    "whereB-9.2",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-9.2>
-    1, 2, true
-    -- </whereB-9.2>
-    })
-
-test:do_execsql_test(
-    "whereB-9.3",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-9.3>
-    1, 2, true
-    -- </whereB-9.3>
-    })
-
-test:do_execsql_test(
-    "whereB-9.4",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-9.4>
-    1, 2, true
-    -- </whereB-9.4>
-    })
-
-test:do_execsql_test(
-    "whereB-9.100",
-    [[
-        DROP INDEX t2b ON t2;
-        SELECT x, a, y=b FROM t1, t2 WHERE y=b;
-    ]],
-    {
-    -- <whereB-9.100>
-    1, 2, true
-    -- </whereB-9.100>
-    })
-
-test:do_execsql_test(
-    "whereB-9.101",
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE b=y;
-    ]],
-    {
-    -- <whereB-9.101>
-    1, 2, true
-    -- </whereB-9.101>
-    })
-
-test:do_execsql_test(
-    "whereB-9.102",
-    -- In this case the unary "+" operator shouldn't
-    -- affect result set of query.
-    [[
-        SELECT x, a, y=b FROM t1, t2 WHERE +y=+b;
-    ]],
-    {
-    -- <whereB-9.102>
-    1, 2, true
-    -- </whereB-9.102>
-    })
-
-test:finish_test()
-
diff --git a/test/sql-tap/whereC.test.lua b/test/sql-tap/whereC.test.lua
index 89459dee3..58c049553 100755
--- a/test/sql-tap/whereC.test.lua
+++ b/test/sql-tap/whereC.test.lua
@@ -55,9 +55,9 @@ test:do_execsql_test(
 test:test("main", function()
     local data = {{"SELECT i FROM t1 WHERE a=1 AND b=2 AND i>3",         {4, 5}},
                   -- {"SELECT i FROM t1 WHERE rowid='12'",                  {12}},
-                  {"SELECT i FROM t1 WHERE a=1 AND b='2'",               {3, 4, 5}},
-                  {"SELECT i FROM t1 WHERE a=1 AND b='2' AND i>'3'",     {4, 5}},
-                  {"SELECT i FROM t1 WHERE a=1 AND b='2' AND i<5",       {3, 4}},
+                  {"SELECT i FROM t1 WHERE a=1 AND b=2",               {3, 4, 5}},
+                  {"SELECT i FROM t1 WHERE a=1 AND b=2 AND i>3",     {4, 5}},
+                  {"SELECT i FROM t1 WHERE a=1 AND b=2 AND i<5",       {3, 4}},
                   {"SELECT i FROM t1 WHERE a=2 AND b=2 AND i<12",        {10, 11}},
                   {"SELECT i FROM t1 WHERE a IN(1, 2) AND b=2 AND i<11", {3, 4, 5, 10}},
                   {"SELECT i FROM t1 WHERE a=2 AND b=2 AND i BETWEEN 10 AND 12", {10, 11, 12}},
@@ -66,7 +66,7 @@ test:test("main", function()
                   {"SELECT i FROM t1 WHERE a=2 AND b=2 AND i BETWEEN 12 AND 10", {}},
                   {"SELECT i FROM t1 WHERE a=2 AND b=2 AND i<NULL",      {}},
                   {"SELECT i FROM t1 WHERE a=2 AND b=2 AND i>=NULL",     {}},
-                  {"SELECT i FROM t1 WHERE a=1 AND b='2' AND i<4.5",     {3, 4}}}
+                  {"SELECT i FROM t1 WHERE a=1 AND b=2 AND i<4.5",     {3, 4}}}
                   -- {"SELECT i FROM t1 WHERE rowid IS '12'",               {12}}}
 
     for tn, t in ipairs(data) do
diff --git a/test/sql/types.result b/test/sql/types.result
index 54aff460e..70fbbc5a2 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -2155,3 +2155,623 @@ box.execute([[SELECT * FROM "s" WHERE "id" = ?;]])
 s:drop()
 ---
 ...
+--
+-- gh-3809: Make sure there are no implicit casts during
+-- assignment, except for the implicit cast between numeric
+-- values.
+--
+-- Check INSERT.
+box.execute([[CREATE TABLE ti (a INT PRIMARY KEY AUTOINCREMENT, i INTEGER);]])
+---
+- row_count: 1
+...
+box.execute([[CREATE TABLE td (a INT PRIMARY KEY AUTOINCREMENT, d DOUBLE);]])
+---
+- row_count: 1
+...
+box.execute([[CREATE TABLE tb (a INT PRIMARY KEY AUTOINCREMENT, b BOOLEAN);]])
+---
+- row_count: 1
+...
+box.execute([[CREATE TABLE tt (a INT PRIMARY KEY AUTOINCREMENT, t TEXT);]])
+---
+- row_count: 1
+...
+box.execute([[CREATE TABLE tv (a INT PRIMARY KEY AUTOINCREMENT, v VARBINARY);]])
+---
+- row_count: 1
+...
+box.execute([[CREATE TABLE ts (a INT PRIMARY KEY AUTOINCREMENT, s SCALAR);]])
+---
+- row_count: 1
+...
+box.execute([[INSERT INTO ti(i) VALUES (NULL);]])
+---
+- autoincrement_ids:
+  - 1
+  row_count: 1
+...
+box.execute([[INSERT INTO ti(i) VALUES (11);]])
+---
+- autoincrement_ids:
+  - 2
+  row_count: 1
+...
+box.execute([[INSERT INTO ti(i) VALUES (100000000000000000000000000000000.1);]])
+---
+- null
+- 'Type mismatch: can not convert 1.0e+32 to integer'
+...
+box.execute([[INSERT INTO ti(i) VALUES (33.0);]])
+---
+- autoincrement_ids:
+  - 3
+  row_count: 1
+...
+box.execute([[INSERT INTO ti(i) VALUES (true);]])
+---
+- null
+- 'Type mismatch: can not convert TRUE to integer'
+...
+box.execute([[INSERT INTO ti(i) VALUES ('33');]])
+---
+- null
+- 'Type mismatch: can not convert 33 to integer'
+...
+box.execute([[INSERT INTO ti(i) VALUES (X'3434');]])
+---
+- null
+- 'Type mismatch: can not convert varbinary to integer'
+...
+box.execute([[SELECT * FROM ti;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: I
+    type: integer
+  rows:
+  - [1, null]
+  - [2, 11]
+  - [3, 33]
+...
+box.execute([[INSERT INTO td(d) VALUES (NULL);]])
+---
+- autoincrement_ids:
+  - 1
+  row_count: 1
+...
+box.execute([[INSERT INTO td(d) VALUES (11);]])
+---
+- autoincrement_ids:
+  - 2
+  row_count: 1
+...
+box.execute([[INSERT INTO td(d) VALUES (100000000000000001);;]])
+---
+- null
+- Syntax error at line 1 near ';'
+...
+box.execute([[INSERT INTO td(d) VALUES (22.2);]])
+---
+- autoincrement_ids:
+  - 3
+  row_count: 1
+...
+box.execute([[INSERT INTO td(d) VALUES (true);]])
+---
+- null
+- 'Type mismatch: can not convert TRUE to double'
+...
+box.execute([[INSERT INTO td(d) VALUES ('33');]])
+---
+- null
+- 'Type mismatch: can not convert 33 to double'
+...
+box.execute([[INSERT INTO td(d) VALUES (X'3434');]])
+---
+- null
+- 'Type mismatch: can not convert varbinary to double'
+...
+box.execute([[SELECT * FROM td;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: D
+    type: double
+  rows:
+  - [1, null]
+  - [2, 11]
+  - [3, 22.2]
+...
+box.execute([[INSERT INTO tb(b) VALUES (NULL);]])
+---
+- autoincrement_ids:
+  - 1
+  row_count: 1
+...
+box.execute([[INSERT INTO tb(b) VALUES (11);]])
+---
+- null
+- 'Type mismatch: can not convert 11 to boolean'
+...
+box.execute([[INSERT INTO tb(b) VALUES (22.2);]])
+---
+- null
+- 'Type mismatch: can not convert 22.2 to boolean'
+...
+box.execute([[INSERT INTO tb(b) VALUES (true);]])
+---
+- autoincrement_ids:
+  - 2
+  row_count: 1
+...
+box.execute([[INSERT INTO tb(b) VALUES ('33');]])
+---
+- null
+- 'Type mismatch: can not convert 33 to boolean'
+...
+box.execute([[INSERT INTO tb(b) VALUES (X'3434');]])
+---
+- null
+- 'Type mismatch: can not convert varbinary to boolean'
+...
+box.execute([[SELECT * FROM tb;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: B
+    type: boolean
+  rows:
+  - [1, null]
+  - [2, true]
+...
+box.execute([[INSERT INTO tt(t) VALUES (NULL);]])
+---
+- autoincrement_ids:
+  - 1
+  row_count: 1
+...
+box.execute([[INSERT INTO tt(t) VALUES (11);]])
+---
+- null
+- 'Type mismatch: can not convert 11 to string'
+...
+box.execute([[INSERT INTO tt(t) VALUES (22.2);]])
+---
+- null
+- 'Type mismatch: can not convert 22.2 to string'
+...
+box.execute([[INSERT INTO tt(t) VALUES (true);]])
+---
+- null
+- 'Type mismatch: can not convert TRUE to string'
+...
+box.execute([[INSERT INTO tt(t) VALUES ('33');]])
+---
+- autoincrement_ids:
+  - 2
+  row_count: 1
+...
+box.execute([[INSERT INTO tt(t) VALUES (X'3434');]])
+---
+- null
+- 'Type mismatch: can not convert varbinary to string'
+...
+box.execute([[SELECT * FROM tt;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: T
+    type: string
+  rows:
+  - [1, null]
+  - [2, '33']
+...
+box.execute([[INSERT INTO tv(v) VALUES (NULL);]])
+---
+- autoincrement_ids:
+  - 1
+  row_count: 1
+...
+box.execute([[INSERT INTO tv(v) VALUES (11);]])
+---
+- null
+- 'Type mismatch: can not convert 11 to varbinary'
+...
+box.execute([[INSERT INTO tv(v) VALUES (22.2);]])
+---
+- null
+- 'Type mismatch: can not convert 22.2 to varbinary'
+...
+box.execute([[INSERT INTO tv(v) VALUES (true);]])
+---
+- null
+- 'Type mismatch: can not convert TRUE to varbinary'
+...
+box.execute([[INSERT INTO tv(v) VALUES ('33');]])
+---
+- null
+- 'Type mismatch: can not convert 33 to varbinary'
+...
+box.execute([[INSERT INTO tv(v) VALUES (X'3434');]])
+---
+- autoincrement_ids:
+  - 2
+  row_count: 1
+...
+box.execute([[SELECT * FROM tv;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: V
+    type: varbinary
+  rows:
+  - [1, null]
+  - [2, '44']
+...
+box.execute([[INSERT INTO ts(s) VALUES (NULL);]])
+---
+- autoincrement_ids:
+  - 1
+  row_count: 1
+...
+box.execute([[INSERT INTO ts(s) VALUES (11);]])
+---
+- autoincrement_ids:
+  - 2
+  row_count: 1
+...
+box.execute([[INSERT INTO ts(s) VALUES (22.2);]])
+---
+- autoincrement_ids:
+  - 3
+  row_count: 1
+...
+box.execute([[INSERT INTO ts(s) VALUES (true);]])
+---
+- autoincrement_ids:
+  - 4
+  row_count: 1
+...
+box.execute([[INSERT INTO ts(s) VALUES ('33');]])
+---
+- autoincrement_ids:
+  - 5
+  row_count: 1
+...
+box.execute([[INSERT INTO ts(s) VALUES (X'3434');]])
+---
+- autoincrement_ids:
+  - 6
+  row_count: 1
+...
+box.execute([[SELECT * FROM ts;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: S
+    type: scalar
+  rows:
+  - [1, null]
+  - [2, 11]
+  - [3, 22.2]
+  - [4, true]
+  - [5, '33']
+  - [6, '44']
+...
+-- Check for UPDATE.
+box.execute([[DELETE FROM ti;]])
+---
+- row_count: 3
+...
+box.execute([[DELETE FROM td;]])
+---
+- row_count: 3
+...
+box.execute([[DELETE FROM tb;]])
+---
+- row_count: 2
+...
+box.execute([[DELETE FROM tt;]])
+---
+- row_count: 2
+...
+box.execute([[DELETE FROM tv;]])
+---
+- row_count: 2
+...
+box.execute([[DELETE FROM ts;]])
+---
+- row_count: 6
+...
+box.execute([[INSERT INTO ti VALUES(1, NULL);]])
+---
+- row_count: 1
+...
+box.execute([[INSERT INTO td VALUES(1, NULL);]])
+---
+- row_count: 1
+...
+box.execute([[INSERT INTO tb VALUES(1, NULL);]])
+---
+- row_count: 1
+...
+box.execute([[INSERT INTO tt VALUES(1, NULL);]])
+---
+- row_count: 1
+...
+box.execute([[INSERT INTO tv VALUES(1, NULL);]])
+---
+- row_count: 1
+...
+box.execute([[INSERT INTO ts VALUES(1, NULL);]])
+---
+- row_count: 1
+...
+box.execute([[SELECT * FROM ti, td, tb, tt, tv, ts;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: I
+    type: integer
+  - name: A
+    type: integer
+  - name: D
+    type: double
+  - name: A
+    type: integer
+  - name: B
+    type: boolean
+  - name: A
+    type: integer
+  - name: T
+    type: string
+  - name: A
+    type: integer
+  - name: V
+    type: varbinary
+  - name: A
+    type: integer
+  - name: S
+    type: scalar
+  rows:
+  - [1, null, 1, null, 1, null, 1, null, 1, null, 1, null]
+...
+box.execute([[UPDATE ti SET i = NULL WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE ti SET i = 11 WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE ti SET i = 100000000000000000000000000000000.1 WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert 1.0e+32 to integer'
+...
+box.execute([[UPDATE ti SET i = 33.0 WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE ti SET i = true WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert TRUE to integer'
+...
+box.execute([[UPDATE ti SET i = '33' WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert 33 to integer'
+...
+box.execute([[UPDATE ti SET i = X'3434' WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert varbinary to integer'
+...
+box.execute([[SELECT * FROM ti;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: I
+    type: integer
+  rows:
+  - [1, 33]
+...
+box.execute([[UPDATE td SET d = NULL WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE td SET d = 11 WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE td SET d = 100000000000000001 WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE td SET d = 22.2 WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE td SET d = true WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert TRUE to double'
+...
+box.execute([[UPDATE td SET d = '33' WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert 33 to double'
+...
+box.execute([[UPDATE td SET d = X'3434' WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert varbinary to double'
+...
+box.execute([[SELECT * FROM td;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: D
+    type: double
+  rows:
+  - [1, 22.2]
+...
+box.execute([[UPDATE tb SET b = NULL WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE tb SET b = 11 WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert 11 to boolean'
+...
+box.execute([[UPDATE tb SET b = 22.2 WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert 22.2 to boolean'
+...
+box.execute([[UPDATE tb SET b = true WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE tb SET b = '33' WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert 33 to boolean'
+...
+box.execute([[UPDATE tb SET b = X'3434' WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert varbinary to boolean'
+...
+box.execute([[SELECT * FROM tb;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: B
+    type: boolean
+  rows:
+  - [1, true]
+...
+box.execute([[UPDATE tt SET t = NULL WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE tt SET t = 11 WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert 11 to string'
+...
+box.execute([[UPDATE tt SET t = 22.2 WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert 22.2 to string'
+...
+box.execute([[UPDATE tt SET t = true WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert TRUE to string'
+...
+box.execute([[UPDATE tt SET t = '33' WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE tt SET t = X'3434' WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert varbinary to string'
+...
+box.execute([[SELECT * FROM tt;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: T
+    type: string
+  rows:
+  - [1, '33']
+...
+box.execute([[UPDATE tv SET v = NULL WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE tv SET v = 11 WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert 11 to varbinary'
+...
+box.execute([[UPDATE tv SET v = 22.2 WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert 22.2 to varbinary'
+...
+box.execute([[UPDATE tv SET v = true WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert TRUE to varbinary'
+...
+box.execute([[UPDATE tv SET v = '33' WHERE a = 1;]])
+---
+- null
+- 'Type mismatch: can not convert 33 to varbinary'
+...
+box.execute([[UPDATE tv SET v = X'3434' WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[SELECT * FROM tv;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: V
+    type: varbinary
+  rows:
+  - [1, '44']
+...
+box.execute([[UPDATE ts SET s = NULL WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE ts SET s = 11 WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE ts SET s = 22.2 WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE ts SET s = true WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE ts SET s = '33' WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[UPDATE ts SET s = X'3434' WHERE a = 1;]])
+---
+- row_count: 1
+...
+box.execute([[SELECT * FROM ts;]])
+---
+- metadata:
+  - name: A
+    type: integer
+  - name: S
+    type: scalar
+  rows:
+  - [1, '44']
+...
diff --git a/test/sql/types.test.lua b/test/sql/types.test.lua
index bd14b342d..2dc70f3c5 100644
--- a/test/sql/types.test.lua
+++ b/test/sql/types.test.lua
@@ -487,3 +487,132 @@ s:format({ \
 
 box.execute([[SELECT * FROM "s" WHERE "id" = ?;]])
 s:drop()
+
+--
+-- gh-3809: Make sure there are no implicit casts during
+-- assignment, except for the implicit cast between numeric
+-- values.
+--
+
+-- Check INSERT.
+box.execute([[CREATE TABLE ti (a INT PRIMARY KEY AUTOINCREMENT, i INTEGER);]])
+box.execute([[CREATE TABLE td (a INT PRIMARY KEY AUTOINCREMENT, d DOUBLE);]])
+box.execute([[CREATE TABLE tb (a INT PRIMARY KEY AUTOINCREMENT, b BOOLEAN);]])
+box.execute([[CREATE TABLE tt (a INT PRIMARY KEY AUTOINCREMENT, t TEXT);]])
+box.execute([[CREATE TABLE tv (a INT PRIMARY KEY AUTOINCREMENT, v VARBINARY);]])
+box.execute([[CREATE TABLE ts (a INT PRIMARY KEY AUTOINCREMENT, s SCALAR);]])
+
+box.execute([[INSERT INTO ti(i) VALUES (NULL);]])
+box.execute([[INSERT INTO ti(i) VALUES (11);]])
+box.execute([[INSERT INTO ti(i) VALUES (100000000000000000000000000000000.1);]])
+box.execute([[INSERT INTO ti(i) VALUES (33.0);]])
+box.execute([[INSERT INTO ti(i) VALUES (true);]])
+box.execute([[INSERT INTO ti(i) VALUES ('33');]])
+box.execute([[INSERT INTO ti(i) VALUES (X'3434');]])
+box.execute([[SELECT * FROM ti;]])
+
+box.execute([[INSERT INTO td(d) VALUES (NULL);]])
+box.execute([[INSERT INTO td(d) VALUES (11);]])
+box.execute([[INSERT INTO td(d) VALUES (100000000000000001);;]])
+box.execute([[INSERT INTO td(d) VALUES (22.2);]])
+box.execute([[INSERT INTO td(d) VALUES (true);]])
+box.execute([[INSERT INTO td(d) VALUES ('33');]])
+box.execute([[INSERT INTO td(d) VALUES (X'3434');]])
+box.execute([[SELECT * FROM td;]])
+
+box.execute([[INSERT INTO tb(b) VALUES (NULL);]])
+box.execute([[INSERT INTO tb(b) VALUES (11);]])
+box.execute([[INSERT INTO tb(b) VALUES (22.2);]])
+box.execute([[INSERT INTO tb(b) VALUES (true);]])
+box.execute([[INSERT INTO tb(b) VALUES ('33');]])
+box.execute([[INSERT INTO tb(b) VALUES (X'3434');]])
+box.execute([[SELECT * FROM tb;]])
+
+box.execute([[INSERT INTO tt(t) VALUES (NULL);]])
+box.execute([[INSERT INTO tt(t) VALUES (11);]])
+box.execute([[INSERT INTO tt(t) VALUES (22.2);]])
+box.execute([[INSERT INTO tt(t) VALUES (true);]])
+box.execute([[INSERT INTO tt(t) VALUES ('33');]])
+box.execute([[INSERT INTO tt(t) VALUES (X'3434');]])
+box.execute([[SELECT * FROM tt;]])
+
+box.execute([[INSERT INTO tv(v) VALUES (NULL);]])
+box.execute([[INSERT INTO tv(v) VALUES (11);]])
+box.execute([[INSERT INTO tv(v) VALUES (22.2);]])
+box.execute([[INSERT INTO tv(v) VALUES (true);]])
+box.execute([[INSERT INTO tv(v) VALUES ('33');]])
+box.execute([[INSERT INTO tv(v) VALUES (X'3434');]])
+box.execute([[SELECT * FROM tv;]])
+
+box.execute([[INSERT INTO ts(s) VALUES (NULL);]])
+box.execute([[INSERT INTO ts(s) VALUES (11);]])
+box.execute([[INSERT INTO ts(s) VALUES (22.2);]])
+box.execute([[INSERT INTO ts(s) VALUES (true);]])
+box.execute([[INSERT INTO ts(s) VALUES ('33');]])
+box.execute([[INSERT INTO ts(s) VALUES (X'3434');]])
+box.execute([[SELECT * FROM ts;]])
+
+-- Check for UPDATE.
+box.execute([[DELETE FROM ti;]])
+box.execute([[DELETE FROM td;]])
+box.execute([[DELETE FROM tb;]])
+box.execute([[DELETE FROM tt;]])
+box.execute([[DELETE FROM tv;]])
+box.execute([[DELETE FROM ts;]])
+box.execute([[INSERT INTO ti VALUES(1, NULL);]])
+box.execute([[INSERT INTO td VALUES(1, NULL);]])
+box.execute([[INSERT INTO tb VALUES(1, NULL);]])
+box.execute([[INSERT INTO tt VALUES(1, NULL);]])
+box.execute([[INSERT INTO tv VALUES(1, NULL);]])
+box.execute([[INSERT INTO ts VALUES(1, NULL);]])
+box.execute([[SELECT * FROM ti, td, tb, tt, tv, ts;]])
+
+box.execute([[UPDATE ti SET i = NULL WHERE a = 1;]])
+box.execute([[UPDATE ti SET i = 11 WHERE a = 1;]])
+box.execute([[UPDATE ti SET i = 100000000000000000000000000000000.1 WHERE a = 1;]])
+box.execute([[UPDATE ti SET i = 33.0 WHERE a = 1;]])
+box.execute([[UPDATE ti SET i = true WHERE a = 1;]])
+box.execute([[UPDATE ti SET i = '33' WHERE a = 1;]])
+box.execute([[UPDATE ti SET i = X'3434' WHERE a = 1;]])
+box.execute([[SELECT * FROM ti;]])
+
+box.execute([[UPDATE td SET d = NULL WHERE a = 1;]])
+box.execute([[UPDATE td SET d = 11 WHERE a = 1;]])
+box.execute([[UPDATE td SET d = 100000000000000001 WHERE a = 1;]])
+box.execute([[UPDATE td SET d = 22.2 WHERE a = 1;]])
+box.execute([[UPDATE td SET d = true WHERE a = 1;]])
+box.execute([[UPDATE td SET d = '33' WHERE a = 1;]])
+box.execute([[UPDATE td SET d = X'3434' WHERE a = 1;]])
+box.execute([[SELECT * FROM td;]])
+
+box.execute([[UPDATE tb SET b = NULL WHERE a = 1;]])
+box.execute([[UPDATE tb SET b = 11 WHERE a = 1;]])
+box.execute([[UPDATE tb SET b = 22.2 WHERE a = 1;]])
+box.execute([[UPDATE tb SET b = true WHERE a = 1;]])
+box.execute([[UPDATE tb SET b = '33' WHERE a = 1;]])
+box.execute([[UPDATE tb SET b = X'3434' WHERE a = 1;]])
+box.execute([[SELECT * FROM tb;]])
+
+box.execute([[UPDATE tt SET t = NULL WHERE a = 1;]])
+box.execute([[UPDATE tt SET t = 11 WHERE a = 1;]])
+box.execute([[UPDATE tt SET t = 22.2 WHERE a = 1;]])
+box.execute([[UPDATE tt SET t = true WHERE a = 1;]])
+box.execute([[UPDATE tt SET t = '33' WHERE a = 1;]])
+box.execute([[UPDATE tt SET t = X'3434' WHERE a = 1;]])
+box.execute([[SELECT * FROM tt;]])
+
+box.execute([[UPDATE tv SET v = NULL WHERE a = 1;]])
+box.execute([[UPDATE tv SET v = 11 WHERE a = 1;]])
+box.execute([[UPDATE tv SET v = 22.2 WHERE a = 1;]])
+box.execute([[UPDATE tv SET v = true WHERE a = 1;]])
+box.execute([[UPDATE tv SET v = '33' WHERE a = 1;]])
+box.execute([[UPDATE tv SET v = X'3434' WHERE a = 1;]])
+box.execute([[SELECT * FROM tv;]])
+
+box.execute([[UPDATE ts SET s = NULL WHERE a = 1;]])
+box.execute([[UPDATE ts SET s = 11 WHERE a = 1;]])
+box.execute([[UPDATE ts SET s = 22.2 WHERE a = 1;]])
+box.execute([[UPDATE ts SET s = true WHERE a = 1;]])
+box.execute([[UPDATE ts SET s = '33' WHERE a = 1;]])
+box.execute([[UPDATE ts SET s = X'3434' WHERE a = 1;]])
+box.execute([[SELECT * FROM ts;]])
-- 
2.25.1

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [Tarantool-patches] [PATCH v4 5/5] sql: properly check arguments of functions
  2020-07-13  5:32 [Tarantool-patches] [PATCH v4 0/5] Change implicit cast for assignment imeevma
                   ` (3 preceding siblings ...)
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 4/5] sql: change implicit cast for assignment imeevma
@ 2020-07-13  5:33 ` imeevma
  2020-07-13 14:56   ` Nikita Pettik
  4 siblings, 1 reply; 11+ messages in thread
From: imeevma @ 2020-07-13  5:33 UTC (permalink / raw)
  To: korablev, tsafin, tarantool-patches

After this patch, the function arguments will be checked using ApplyType
opcode before they are passed to the function. This means that the rules
for implicitly casting values ​​that were specified as arguments are
defined by this opcode.

Closes #4159
---
 src/box/sql/expr.c                          |    5 +
 src/box/sql/func.c                          |  360 +++-
 src/box/sql/select.c                        |   31 +
 src/box/sql/sqlInt.h                        |   32 +
 src/box/sql/vdbe.c                          |   12 +-
 test/sql-tap/cse.test.lua                   |    8 +-
 test/sql-tap/func.test.lua                  |   48 +-
 test/sql-tap/orderby1.test.lua              |    2 +-
 test/sql-tap/position.test.lua              |    6 +-
 test/sql/boolean.result                     |   32 +-
 test/sql/gh-4159-function-argumens.result   | 2131 +++++++++++++++++--
 test/sql/gh-4159-function-argumens.test.sql |  272 +++
 test/sql/types.result                       |   76 +-
 test/sql/types.test.lua                     |    2 +-
 14 files changed, 2584 insertions(+), 433 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 7aee240a3..aa5477c6a 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -4104,6 +4104,11 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			} else {
 				r1 = 0;
 			}
+			if (func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
+				struct func_sql_builtin *f =
+					(struct func_sql_builtin *)func;
+				sql_emit_func_types(v, &f->args, r1, nFarg);
+			}
 			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
 				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
 						  (char *)coll, P4_COLLSEQ);
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 9d4c26081..66edc3792 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -459,17 +459,10 @@ static void
 lengthFunc(sql_context * context, int argc, sql_value ** argv)
 {
 	int len;
-
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
 	switch (sql_value_type(argv[0])) {
-	case MP_BIN:
-	case MP_ARRAY:
-	case MP_MAP:
-	case MP_INT:
-	case MP_UINT:
-	case MP_BOOL:
-	case MP_DOUBLE:{
+	case MP_BIN: {
 			sql_result_uint(context, sql_value_bytes(argv[0]));
 			break;
 		}
@@ -482,6 +475,7 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
 			break;
 		}
 	default:{
+		assert(sql_value_type(argv[0]) == MP_NIL);
 			sql_result_null(context);
 			break;
 		}
@@ -510,32 +504,16 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 		sql_result_uint(context, -value);
 		break;
 	}
-	case MP_NIL:{
-			/* IMP: R-37434-19929 Abs(X) returns NULL if X is NULL. */
-			sql_result_null(context);
-			break;
-		}
-	case MP_BOOL:
-	case MP_BIN:
-	case MP_ARRAY:
-	case MP_MAP: {
-		diag_set(ClientError, ER_INCONSISTENT_TYPES, "number",
-			 mem_type_to_str(argv[0]));
-		context->is_aborted = true;
-		return;
+	case MP_DOUBLE: {
+		double rVal = sql_value_double(argv[0]);
+		if (rVal < 0)
+			rVal = -rVal;
+		sql_result_double(context, rVal);
+		break;
 	}
-	default:{
-			/* Because sql_value_double() returns 0.0 if the argument is not
-			 * something that can be converted into a number, we have:
-			 * IMP: R-01992-00519 Abs(X) returns 0.0 if X is a string or blob
-			 * that cannot be converted to a numeric value.
-			 */
-			double rVal = sql_value_double(argv[0]);
-			if (rVal < 0)
-				rVal = -rVal;
-			sql_result_double(context, rVal);
-			break;
-		}
+	default:
+		assert(sql_value_type(argv[0]) == MP_NIL);
+		sql_result_null(context);
 	}
 }
 
@@ -561,22 +539,8 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
 
 	if (haystack_type == MP_NIL || needle_type == MP_NIL)
 		return;
-	/*
-	 * Position function can be called only with string
-	 * or blob params.
-	 */
-	struct Mem *inconsistent_type_arg = NULL;
-	if (needle_type != MP_STR && needle_type != MP_BIN)
-		inconsistent_type_arg = needle;
-	if (haystack_type != MP_STR && haystack_type != MP_BIN)
-		inconsistent_type_arg = haystack;
-	if (inconsistent_type_arg != NULL) {
-		diag_set(ClientError, ER_INCONSISTENT_TYPES,
-			 "text or varbinary",
-			 mem_type_to_str(inconsistent_type_arg));
-		context->is_aborted = true;
-		return;
-	}
+	assert(needle_type == MP_STR || needle_type == MP_BIN);
+	assert(haystack_type == MP_STR || haystack_type == MP_BIN);
 	/*
 	 * Both params of Position function must be of the same
 	 * type.
@@ -731,6 +695,9 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 		return;
 	}
 	p0type = sql_value_type(argv[0]);
+	assert(p0type == MP_NIL || p0type == MP_STR || p0type == MP_BIN);
+	assert(sql_value_type(argv[1]) == MP_INT ||
+	       sql_value_type(argv[1]) == MP_UINT);
 	p1 = sql_value_int(argv[1]);
 	if (p0type == MP_BIN) {
 		len = sql_value_bytes(argv[0]);
@@ -747,6 +714,8 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
 	}
 	if (argc == 3) {
+		assert(sql_value_type(argv[2]) == MP_INT ||
+		       sql_value_type(argv[2]) == MP_UINT);
 		p2 = sql_value_int(argv[2]);
 		if (p2 < 0) {
 			p2 = -p2;
@@ -811,6 +780,8 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 				      SQL_TRANSIENT);
 	}
 }
+enum field_type func_substr_types[] = {FIELD_TYPE_STRING, FIELD_TYPE_INTEGER,
+				       FIELD_TYPE_INTEGER};
 
 /*
  * Implementation of the round() function
@@ -824,19 +795,14 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 	if (argc == 2) {
 		if (sql_value_is_null(argv[1]))
 			return;
+		assert(sql_value_type(argv[1]) == MP_UINT);
 		n = sql_value_int(argv[1]);
 		if (n < 0)
 			n = 0;
 	}
 	if (sql_value_is_null(argv[0]))
 		return;
-	enum mp_type mp_type = sql_value_type(argv[0]);
-	if (mp_type_is_bloblike(mp_type)) {
-		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 sql_value_to_diag_str(argv[0]), "numeric");
-		context->is_aborted = true;
-		return;
-	}
+	assert(sql_value_type(argv[0]) == MP_DOUBLE);
 	r = sql_value_double(argv[0]);
 	/* If Y==0 and X will fit in a 64-bit int,
 	 * handle the rounding directly,
@@ -852,6 +818,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 	sql_result_double(context, r);
 }
+enum field_type func_round_types[] = {FIELD_TYPE_DOUBLE, FIELD_TYPE_UNSIGNED};
 
 /*
  * Allocate nByte bytes of space using sqlMalloc(). If the
@@ -891,13 +858,9 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
 	const char *z2;                                                        \
 	int n;                                                                 \
 	UNUSED_PARAMETER(argc);                                                \
-	int arg_type = sql_value_type(argv[0]);                                \
-	if (mp_type_is_bloblike(arg_type)) {                                   \
-		diag_set(ClientError, ER_INCONSISTENT_TYPES, "text",           \
-			 "varbinary");                                         \
-		context->is_aborted = true;                                    \
-		return;                                                        \
-	}                                                                      \
+	assert(sql_value_type(argv[0]) == MP_NIL ||                            \
+	       sql_value_type(argv[0]) == MP_STR ||                            \
+	       sql_value_type(argv[0]) == MP_BIN);                             \
 	z2 = (char *)sql_value_text(argv[0]);                              \
 	n = sql_value_bytes(argv[0]);                                      \
 	/*                                                                     \
@@ -949,6 +912,7 @@ ICU_CASE_CONVERT(Upper);
  * is.  We might as well use the "version()" function as a substitute.
  */
 #define noopFunc sql_func_version /* Substitute function - never called */
+enum field_type func_likelihood_types[] = {FIELD_TYPE_ANY, FIELD_TYPE_DOUBLE};
 
 /*
  * Implementation of random().  Return a random integer.
@@ -973,12 +937,8 @@ randomBlob(sql_context * context, int argc, sql_value ** argv)
 	unsigned char *p;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	if (mp_type_is_bloblike(sql_value_type(argv[0]))) {
-		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 sql_value_to_diag_str(argv[0]), "numeric");
-		context->is_aborted = true;
-		return;
-	}
+	assert(sql_value_type(argv[0]) == MP_NIL ||
+	       sql_value_type(argv[0]) == MP_UINT);
 	n = sql_value_int(argv[0]);
 	if (n < 1)
 		return;
@@ -1218,17 +1178,9 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 	int rhs_type = sql_value_type(argv[0]);
 	int lhs_type = sql_value_type(argv[1]);
 
-	if (lhs_type != MP_STR || rhs_type != MP_STR) {
-		if (lhs_type == MP_NIL || rhs_type == MP_NIL)
-			return;
-		char *inconsistent_type = rhs_type != MP_STR ?
-					  mem_type_to_str(argv[0]) :
-					  mem_type_to_str(argv[1]);
-		diag_set(ClientError, ER_INCONSISTENT_TYPES, "text",
-			 inconsistent_type);
-		context->is_aborted = true;
+	if (lhs_type == MP_NIL || rhs_type == MP_NIL)
 		return;
-	}
+	assert(lhs_type == MP_STR && rhs_type == MP_STR);
 	const char *zB = (const char *) sql_value_text(argv[0]);
 	const char *zA = (const char *) sql_value_text(argv[1]);
 	const char *zB_end = zB + sql_value_bytes(argv[0]);
@@ -1252,6 +1204,8 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 	assert(zB == (const char *) sql_value_text(argv[0]));
 
 	if (argc == 3) {
+		assert(sql_value_type(argv[2]) == MP_NIL ||
+		       sql_value_type(argv[2]) == MP_STR);
 		/*
 		 * The escape character string must consist of a
 		 * single UTF-8 character. Otherwise, return an
@@ -1432,6 +1386,10 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 static void
 unicodeFunc(sql_context * context, int argc, sql_value ** argv)
 {
+	assert(argc == 1);
+	assert(sql_value_type(argv[0]) == MP_NIL ||
+	       sql_value_type(argv[0]) == MP_STR ||
+	       sql_value_type(argv[0]) == MP_BIN);
 	const unsigned char *z = sql_value_text(argv[0]);
 	(void)argc;
 	if (z && z[0])
@@ -1456,10 +1414,11 @@ charFunc(sql_context * context, int argc, sql_value ** argv)
 	for (i = 0; i < argc; i++) {
 		uint64_t x;
 		unsigned c;
-		if (sql_value_type(argv[i]) == MP_INT)
-			x = 0xfffd;
-		else
-			x = sql_value_uint64(argv[i]);
+		enum mp_type type = sql_value_type(argv[i]);
+		if (type == MP_NIL)
+			return;
+		assert(sql_value_type(argv[i]) == MP_UINT);
+		x = sql_value_uint64(argv[i]);
 		if (x > 0x10ffff)
 			x = 0xfffd;
 		c = (unsigned)(x & 0x1fffff);
@@ -1493,6 +1452,9 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
 	const unsigned char *pBlob;
 	char *zHex, *z;
 	assert(argc == 1);
+	assert(sql_value_type(argv[0]) == MP_NIL ||
+	       sql_value_type(argv[0]) == MP_STR ||
+	       sql_value_type(argv[0]) == MP_BIN);
 	UNUSED_PARAMETER(argc);
 	pBlob = sql_value_blob(argv[0]);
 	n = sql_value_bytes(argv[0]);
@@ -1518,6 +1480,8 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
 	i64 n;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
+	assert(sql_value_type(argv[0]) == MP_NIL ||
+	       sql_value_type(argv[0]) == MP_UINT);
 	n = sql_value_int64(argv[0]);
 	if (n < 0)
 		n = 0;
@@ -1550,6 +1514,15 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 
 	assert(argc == 3);
 	UNUSED_PARAMETER(argc);
+	assert(sql_value_type(argv[0]) == MP_NIL ||
+	       sql_value_type(argv[0]) == MP_STR ||
+	       sql_value_type(argv[0]) == MP_BIN);
+	assert(sql_value_type(argv[1]) == MP_NIL ||
+	       sql_value_type(argv[1]) == MP_STR ||
+	       sql_value_type(argv[1]) == MP_BIN);
+	assert(sql_value_type(argv[2]) == MP_NIL ||
+	       sql_value_type(argv[2]) == MP_STR ||
+	       sql_value_type(argv[2]) == MP_BIN);
 	zStr = sql_value_text(argv[0]);
 	if (zStr == 0)
 		return;
@@ -1858,13 +1831,9 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 		1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
 	};
 	assert(argc == 1);
-	enum mp_type mp_type = sql_value_type(argv[0]);
-	if (mp_type_is_bloblike(mp_type)) {
-		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 sql_value_to_diag_str(argv[0]), "text");
-		context->is_aborted = true;
-		return;
-	}
+	assert(sql_value_type(argv[0]) == MP_NIL ||
+	       sql_value_type(argv[0]) == MP_STR ||
+	       sql_value_type(argv[0]) == MP_BIN);
 	zIn = (u8 *) sql_value_text(argv[0]);
 	if (zIn == 0)
 		zIn = (u8 *) "";
@@ -1931,15 +1900,7 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 	int type = sql_value_type(argv[0]);
 	if (type == MP_NIL || p == NULL)
 		return;
-	if (type != MP_DOUBLE && type != MP_INT && type != MP_UINT) {
-		if (mem_apply_numeric_type(argv[0]) != 0) {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 sql_value_to_diag_str(argv[0]), "number");
-			context->is_aborted = true;
-			return;
-		}
-		type = sql_value_type(argv[0]);
-	}
+	assert(mp_type_is_numeric(type));
 	p->cnt++;
 	if (type == MP_INT || type == MP_UINT) {
 		int64_t v = sql_value_int64(argv[0]);
@@ -2230,6 +2191,9 @@ static struct {
 	int param_count;
 	uint32_t min_count;
 	uint32_t max_count;
+	enum field_type *types;
+	enum field_type recurrent_type;
+	bool is_blob_like_str;
 	enum field_type returns;
 	enum func_aggregate aggregate;
 	bool export_to_sql;
@@ -2238,6 +2202,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_NUMBER,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_NUMBER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2250,6 +2217,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_NUMBER,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_NUMBER,
 	 .is_deterministic = false,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
@@ -2264,6 +2234,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2276,6 +2249,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2286,6 +2262,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = SQL_MAX_FUNCTION_ARG,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_UNSIGNED,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_STRING,
 	 .is_deterministic = true,
 	 .aggregate = FUNC_AGGREGATE_NONE,
@@ -2298,6 +2277,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_STRING,
+	 .is_blob_like_str = true,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2310,6 +2292,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_STRING,
+	 .is_blob_like_str = true,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2322,6 +2307,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 2,
 	 .max_count = SQL_MAX_FUNCTION_ARG,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2334,6 +2322,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2348,6 +2339,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2360,6 +2354,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2372,6 +2369,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2384,6 +2384,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2396,6 +2399,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2408,6 +2414,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2420,6 +2429,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2432,6 +2444,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2444,6 +2459,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2456,6 +2474,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2468,6 +2489,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2478,6 +2502,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 2,
 	 .max_count = SQL_MAX_FUNCTION_ARG,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2490,6 +2517,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 1,
 	 .max_count = 2,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_SCALAR,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2502,6 +2532,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_STRING,
+	 .is_blob_like_str = true,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2514,6 +2547,9 @@ static struct {
 	 .param_count = 2,
 	 .min_count = 2,
 	 .max_count = 2,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2528,6 +2564,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2538,6 +2577,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 2,
 	 .max_count = SQL_MAX_FUNCTION_ARG,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2550,6 +2592,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_STRING,
+	 .is_blob_like_str = true,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2564,6 +2609,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2574,6 +2622,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 2,
 	 .max_count = 3,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_STRING,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2586,6 +2637,9 @@ static struct {
 	 .param_count = 2,
 	 .min_count = 2,
 	 .max_count = 2,
+	 .types = func_likelihood_types,
+	 .recurrent_type = field_type_MAX,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_BOOLEAN,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2598,6 +2652,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_BOOLEAN,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2612,6 +2669,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2622,6 +2682,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_STRING,
+	 .is_blob_like_str = true,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2634,6 +2697,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2646,6 +2712,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2660,6 +2729,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2670,6 +2742,9 @@ static struct {
 	 .param_count = 2,
 	 .min_count = 2,
 	 .max_count = 2,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_SCALAR,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2684,6 +2759,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2694,6 +2772,9 @@ static struct {
 	 .param_count = 2,
 	 .min_count = 2,
 	 .max_count = 2,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_STRING,
+	 .is_blob_like_str = true,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2708,6 +2789,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2718,6 +2802,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = SQL_MAX_FUNCTION_ARG,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2730,6 +2817,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2742,6 +2832,9 @@ static struct {
 	 .param_count = 0,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2754,6 +2847,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_UNSIGNED,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_VARBINARY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2766,6 +2862,9 @@ static struct {
 	 .param_count = 3,
 	 .min_count = 3,
 	 .max_count = 3,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_STRING,
+	 .is_blob_like_str = true,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2778,6 +2877,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 1,
 	 .max_count = 2,
+	 .types = func_round_types,
+	 .recurrent_type = field_type_MAX,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2790,6 +2892,9 @@ static struct {
 	 .param_count = 0,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_INTEGER,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2804,6 +2909,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2814,6 +2922,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_STRING,
+	 .is_blob_like_str = true,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2828,6 +2939,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2840,6 +2954,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2850,6 +2967,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 2,
 	 .max_count = 3,
+	 .types = func_substr_types,
+	 .recurrent_type = field_type_MAX,
+	 .is_blob_like_str = true,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2862,6 +2982,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_NUMBER,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_NUMBER,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2876,6 +2999,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2886,6 +3012,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_NUMBER,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_NUMBER,
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
@@ -2898,6 +3027,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 1,
 	 .max_count = 3,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2910,6 +3042,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2922,6 +3057,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_STRING,
+	 .is_blob_like_str = true,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2934,6 +3072,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_BOOLEAN,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2946,6 +3087,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_STRING,
+	 .is_blob_like_str = true,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2958,6 +3102,9 @@ static struct {
 	 .param_count = 0,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_STRING,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2970,6 +3117,9 @@ static struct {
 	 .param_count = 1,
 	 .min_count = 1,
 	 .max_count = 1,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_UNSIGNED,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_VARBINARY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = true,
@@ -2984,6 +3134,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -2996,6 +3149,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = NULL,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -3008,6 +3164,9 @@ static struct {
 	 .param_count = -1,
 	 .min_count = 0,
 	 .max_count = 0,
+	 .types = false,
+	 .recurrent_type = FIELD_TYPE_ANY,
+	 .is_blob_like_str = false,
 	 .returns = FIELD_TYPE_ANY,
 	 .aggregate = FUNC_AGGREGATE_NONE,
 	 .is_deterministic = false,
@@ -3061,6 +3220,9 @@ func_sql_builtin_new(struct func_def *def)
 	func->finalize = sql_builtins[idx].finalize;
 	func->args.min_count = sql_builtins[idx].min_count;
 	func->args.max_count = sql_builtins[idx].max_count;
+	func->args.types = sql_builtins[idx].types;
+	func->args.recurrent_type = sql_builtins[idx].recurrent_type;
+	func->args.is_blob_like_str = sql_builtins[idx].is_blob_like_str;
 	def->param_count = sql_builtins[idx].param_count;
 	def->is_deterministic = sql_builtins[idx].is_deterministic;
 	def->returns = sql_builtins[idx].returns;
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 4b069addb..fe56ede1b 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -124,6 +124,34 @@ clearSelect(sql * db, Select * p, int bFree)
 	}
 }
 
+void
+sql_emit_func_types(struct Vdbe *vdbe, struct sql_builtin_func_args *args,
+		    int reg, uint32_t argc)
+{
+	assert(argc <= args->max_count);
+	enum field_type recurrent_type = args->recurrent_type;
+	assert(args->max_count > 0 || recurrent_type == FIELD_TYPE_ANY);
+	/*
+	 * It makes no sense to check types of the MEMs if all
+	 * arguments should be of type ANY.
+	 */
+	if (recurrent_type == FIELD_TYPE_ANY)
+		return;
+	size_t size = (argc + 1) * sizeof(enum field_type);
+	enum field_type *types = sqlDbMallocZero(sql_get(), size);
+	for (uint32_t i = 0; i < argc; ++i) {
+		if (args->types == NULL)
+			types[i] = args->recurrent_type;
+		else
+			types[i] = args->types[i];
+	}
+	types[argc] = field_type_MAX;
+	sqlVdbeAddOp4(vdbe, OP_ApplyType, reg, argc, 0, (char *)types,
+		      P4_DYNAMIC);
+	if (args->is_blob_like_str)
+		sqlVdbeChangeP5(vdbe, OPFLAG_BLOB_LIKE_STRING);
+}
+
 /*
  * Initialize a SelectDest structure.
  */
@@ -5414,6 +5442,9 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			sqlVdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
 					  (char *)coll, P4_COLLSEQ);
 		}
+		struct func_sql_builtin *f =
+			(struct func_sql_builtin *)pF->func;
+		sql_emit_func_types(v, &f->args, regAgg, nArg);
 		sqlVdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem);
 		sqlVdbeAppendP4(v, pF->func, P4_FUNC);
 		sqlVdbeChangeP5(v, (u8) nArg);
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 6af9d7473..f82ae4eeb 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -265,6 +265,8 @@
 #include <assert.h>
 #include <stddef.h>
 
+struct sql_builtin_func_args;
+
 typedef long long int sql_int64;
 typedef unsigned long long int sql_uint64;
 typedef sql_int64 sql_int64;
@@ -2307,6 +2309,8 @@ struct Parse {
 #define OPFLAG_SYSTEMSP      0x20	/* OP_Open**: set if space pointer
 					 * points to system space.
 					 */
+/** OP_ApplyType: Treat BLOB as STRING. */
+#define OPFLAG_BLOB_LIKE_STRING		0x01
 
 /**
  * Prepare vdbe P5 flags for OP_{IdxInsert, IdxReplace, Update}
@@ -3881,6 +3885,20 @@ sql_index_type_str(struct sql *db, const struct index_def *idx_def);
 void
 sql_emit_table_types(struct Vdbe *v, struct space_def *def, int reg);
 
+/**
+ * Code an OP_ApplyType opcode that will force types for given
+ * range of register starting from @a reg. These values then will
+ * be used as arguments of a function.
+ *
+ * @param vdbe VDBE.
+ * @param args Information about arguments of the function.
+ * @param reg Register where types will be placed.
+ * @param argc Number of arguments.
+ */
+void
+sql_emit_func_types(struct Vdbe *vdbe, struct sql_builtin_func_args *args,
+		    int reg, uint32_t argc);
+
 enum field_type
 sql_type_result(enum field_type lhs, enum field_type rhs);
 
@@ -4406,6 +4424,20 @@ struct sql_builtin_func_args {
 	uint32_t min_count;
 	/** Max number of arguments. */
 	uint32_t max_count;
+	/**
+	 * If function arguments may not be of the same type, all
+	 * argument types are described here.
+	 */
+	enum field_type *types;
+	/**
+	 * Contains the type of arguments if all arguments to the
+	 * function are of the same type.
+	 */
+	enum field_type recurrent_type;
+	/**
+	 * TRUE if the function should treat the BLOB as STRING.
+	 */
+	bool is_blob_like_str;
 };
 
 struct func_sql_builtin {
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 863f38f5d..a4bf84bcc 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2882,7 +2882,7 @@ case OP_Fetch: {
 	break;
 }
 
-/* Opcode: ApplyType P1 P2 * P4 *
+/* Opcode: ApplyType P1 P2 * P4 P5
  * Synopsis: type(r[P1@P2])
  *
  * Check that types of P2 registers starting from register P1 are
@@ -2890,6 +2890,9 @@ case OP_Fetch: {
  * value and the given type are incompatible according to
  * field_mp_plain_type_is_compatible(), but both are numeric,
  * this opcode attempts to convert the value to the type.
+ *
+ * If P5 contains the OPFLAG_BLOB_LIKE_STRING flag, the BLOB
+ * values ​​are processed as if they had the field type STRING.
  */
 case OP_ApplyType: {
 	enum field_type *types = pOp->p4.types;
@@ -2904,6 +2907,13 @@ case OP_ApplyType: {
 			pIn1++;
 			continue;
 		}
+		if ((pOp->p5 & OPFLAG_BLOB_LIKE_STRING) != 0) {
+			if (type == FIELD_TYPE_STRING &&
+			    mem_mp_type(pIn1) == MP_BIN) {
+				pIn1++;
+				continue;
+			}
+		}
 		if (!mp_type_is_numeric(mem_mp_type(pIn1)) ||
 		    !sql_type_is_numeric(type) ||
 		    mem_convert_to_numeric(pIn1, type, false) != 0) {
diff --git a/test/sql-tap/cse.test.lua b/test/sql-tap/cse.test.lua
index 341b6de01..3c2076a1d 100755
--- a/test/sql-tap/cse.test.lua
+++ b/test/sql-tap/cse.test.lua
@@ -195,23 +195,23 @@ test:do_execsql_test(
 
 
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "cse-1.13",
     [[
         SELECT upper(b), typeof(b), b FROM t1
     ]], {
         -- <cse-1.13>
-        "11", "integer", 11, "21", "integer", 21
+        1, "Type mismatch: can not convert 11 to string"
         -- </cse-1.13>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "cse-1.14",
     [[
         SELECT b, typeof(b), upper(b), typeof(b), b FROM t1
     ]], {
         -- <cse-1.14>
-        11, "integer", "11", "integer", 11, 21, "integer", "21", "integer", 21
+        1, "Type mismatch: can not convert 11 to string"
         -- </cse-1.14>
     })
 
diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
index 1d3ef9e2a..ae97994d7 100755
--- a/test/sql-tap/func.test.lua
+++ b/test/sql-tap/func.test.lua
@@ -92,13 +92,13 @@ test:do_execsql_test(
         -- </func-1.3>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-1.4",
     [[
         SELECT coalesce(length(a),-1) FROM t2
     ]], {
         -- <func-1.4>
-        1, -1, 3, -1, 5
+        1, "Type mismatch: can not convert 1 to string"
         -- </func-1.4>
     })
 
@@ -194,23 +194,23 @@ test:do_execsql_test(
         -- </func-2.8>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-2.9",
     [[
         SELECT substr(a,1,1) FROM t2
     ]], {
         -- <func-2.9>
-        "1", "", "3", "", "6"
+        1, "Type mismatch: can not convert 1 to string"
         -- </func-2.9>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-2.10",
     [[
         SELECT substr(a,2,2) FROM t2
     ]], {
         -- <func-2.10>
-        "", "", "45", "", "78"
+        1, "Type mismatch: can not convert 1 to string"
         -- </func-2.10>
     })
 
@@ -412,13 +412,13 @@ test:do_execsql_test(
         -- </func-4.4.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-4.4.2",
     [[
         SELECT abs(t1) FROM tbl1
     ]], {
         -- <func-4.4.2>
-        0.0, 0.0, 0.0, 0.0, 0.0
+        1, "Type mismatch: can not convert this to number"
         -- </func-4.4.2>
     })
 
@@ -502,13 +502,13 @@ test:do_execsql_test(
         -- </func-4.12>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-4.13",
     [[
         SELECT round(t1,2) FROM tbl1
     ]], {
         -- <func-4.13>
-        0.0, 0.0, 0.0, 0.0, 0.0
+        1, "Type mismatch: can not convert this to double"
         -- </func-4.13>
     })
 
@@ -760,13 +760,13 @@ test:do_execsql_test(
         -- </func-5.2>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-5.3",
     [[
         SELECT upper(a), lower(a) FROM t2
     ]], {
         -- <func-5.3>
-        "1","1","","","345","345","","","67890","67890"
+        1, "Type mismatch: can not convert 1 to string"
         -- </func-5.3>
     })
 
@@ -794,13 +794,13 @@ test:do_execsql_test(
         -- </func-6.1>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-6.2",
     [[
         SELECT coalesce(upper(a),'nil') FROM t2
     ]], {
         -- <func-6.2>
-        "1","nil","345","nil","67890"
+        1, "Type mismatch: can not convert 1 to string"
         -- </func-6.2>
     })
 
@@ -893,7 +893,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "func-8.5",
     [[
-        SELECT sum(x) FROM (SELECT '9223372036' || '854775807' AS x
+        SELECT sum(x) FROM (SELECT CAST('9223372036' || '854775807' AS INTEGER) AS x
                             UNION ALL SELECT -9223372036854775807)
     ]], {
         -- <func-8.5>
@@ -904,7 +904,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "func-8.6",
     [[
-        SELECT typeof(sum(x)) FROM (SELECT '9223372036' || '854775807' AS x
+        SELECT typeof(sum(x)) FROM (SELECT CAST('9223372036' || '854775807' AS INTEGER) AS x
                             UNION ALL SELECT -9223372036854775807)
     ]], {
         -- <func-8.6>
@@ -915,7 +915,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "func-8.7",
     [[
-        SELECT typeof(sum(x)) FROM (SELECT '9223372036' || '854775808' AS x
+        SELECT typeof(sum(x)) FROM (SELECT CAST('9223372036' || '854775808' AS INTEGER) AS x
                             UNION ALL SELECT -9223372036854775807)
     ]], {
         -- <func-8.7>
@@ -926,7 +926,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "func-8.8",
     [[
-        SELECT sum(x)>0.0 FROM (SELECT '9223372036' || '854775808' AS x
+        SELECT sum(x)>0.0 FROM (SELECT CAST('9223372036' || '854775808' AS INTEGER) AS x
                             UNION ALL SELECT -9223372036850000000)
     ]], {
         -- <func-8.8>
@@ -982,14 +982,14 @@ test:do_execsql_test(
         -- </func-9.4>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "func-9.5",
     [[
         SELECT length(randomblob(32)), length(randomblob(-5)),
                length(randomblob(2000))
     ]], {
         -- <func-9.5>
-        32, "", 2000
+        1, "Type mismatch: can not convert -5 to unsigned"
         -- </func-9.5>
     })
 
@@ -2918,7 +2918,7 @@ test:do_catchsql_test(
         SELECT ROUND(X'FF')
     ]], {
         -- <func-76.1>
-        1, "Type mismatch: can not convert varbinary to numeric"
+        1, "Type mismatch: can not convert varbinary to double"
         -- </func-76.1>
     })
 
@@ -2928,17 +2928,17 @@ test:do_catchsql_test(
         SELECT RANDOMBLOB(X'FF')
     ]], {
         -- <func-76.2>
-        1, "Type mismatch: can not convert varbinary to numeric"
+        1, "Type mismatch: can not convert varbinary to unsigned"
         -- </func-76.2>
     })
 
-test:do_catchsql_test(
+test:do_execsql_test(
     "func-76.3",
     [[
         SELECT SOUNDEX(X'FF')
     ]], {
         -- <func-76.3>
-        1, "Type mismatch: can not convert varbinary to text"
+        "?000"
         -- </func-76.3>
     })
 
diff --git a/test/sql-tap/orderby1.test.lua b/test/sql-tap/orderby1.test.lua
index 51e8d301f..95a8de487 100755
--- a/test/sql-tap/orderby1.test.lua
+++ b/test/sql-tap/orderby1.test.lua
@@ -735,7 +735,7 @@ test:do_execsql_test(
         SELECT (
           SELECT 'hardware' FROM ( 
             SELECT 'software' ORDER BY 'firmware' ASC, 'sportswear' DESC
-          ) GROUP BY 1 HAVING length(b) <> 0
+          ) GROUP BY 1 HAVING length(CAST(b AS STRING)) <> 0
         )
         FROM abc;
     ]], {
diff --git a/test/sql-tap/position.test.lua b/test/sql-tap/position.test.lua
index e0455abc9..0d4f6f371 100755
--- a/test/sql-tap/position.test.lua
+++ b/test/sql-tap/position.test.lua
@@ -228,7 +228,7 @@ test:do_test(
         return test:catchsql "SELECT position(34, 12345);"
     end, {
         -- <position-1.23>
-        1, "Inconsistent types: expected text or varbinary got unsigned"
+        1, "Type mismatch: can not convert 34 to string"
         -- </position-1.23>
     })
 
@@ -238,7 +238,7 @@ test:do_test(
         return test:catchsql "SELECT position(34, 123456.78);"
     end, {
         -- <position-1.24>
-        1, "Inconsistent types: expected text or varbinary got real"
+        1, "Type mismatch: can not convert 34 to string"
         -- </position-1.24>
     })
 
@@ -248,7 +248,7 @@ test:do_test(
         return test:catchsql "SELECT position(x'3334', 123456.78);"
     end, {
         -- <position-1.25>
-        1, "Inconsistent types: expected text or varbinary got real"
+        1, "Type mismatch: can not convert 123456.78 to string"
         -- </position-1.25>
     })
 
diff --git a/test/sql/boolean.result b/test/sql/boolean.result
index 112e41a12..7e7f97284 100644
--- a/test/sql/boolean.result
+++ b/test/sql/boolean.result
@@ -276,29 +276,17 @@ SELECT is_boolean('true');
 SELECT abs(a) FROM t0;
  | ---
  | - null
- | - 'Inconsistent types: expected number got boolean'
+ | - 'Type mismatch: can not convert FALSE to number'
  | ...
 SELECT lower(a) FROM t0;
  | ---
- | - metadata:
- |   - name: lower(a)
- |     type: string
- |   rows:
- |   - ['false']
- |   - ['true']
- |   - [null]
- |   - [null]
+ | - null
+ | - 'Type mismatch: can not convert FALSE to string'
  | ...
 SELECT upper(a) FROM t0;
  | ---
- | - metadata:
- |   - name: upper(a)
- |     type: string
- |   rows:
- |   - ['FALSE']
- |   - ['TRUE']
- |   - [null]
- |   - [null]
+ | - null
+ | - 'Type mismatch: can not convert FALSE to string'
  | ...
 SELECT quote(a) FROM t0;
  | ---
@@ -314,14 +302,8 @@ SELECT quote(a) FROM t0;
 -- gh-4462: LENGTH didn't take BOOLEAN arguments.
 SELECT length(a) FROM t0;
  | ---
- | - metadata:
- |   - name: length(a)
- |     type: integer
- |   rows:
- |   - [5]
- |   - [4]
- |   - [null]
- |   - [null]
+ | - null
+ | - 'Type mismatch: can not convert FALSE to string'
  | ...
 SELECT typeof(a) FROM t0;
  | ---
diff --git a/test/sql/gh-4159-function-argumens.result b/test/sql/gh-4159-function-argumens.result
index 48bd550a4..4a276d65b 100644
--- a/test/sql/gh-4159-function-argumens.result
+++ b/test/sql/gh-4159-function-argumens.result
@@ -19,6 +19,54 @@ SELECT abs(1, 2);
  | - 'Wrong number of arguments is passed to ABS(): expected 1, got 2'
  | ...
 
+SELECT abs(NULL);
+ | ---
+ | - metadata:
+ |   - name: abs(NULL)
+ |     type: number
+ |   rows:
+ |   - [null]
+ | ...
+SELECT abs(1);
+ | ---
+ | - metadata:
+ |   - name: abs(1)
+ |     type: number
+ |   rows:
+ |   - [1]
+ | ...
+SELECT abs(-1);
+ | ---
+ | - metadata:
+ |   - name: abs(-1)
+ |     type: number
+ |   rows:
+ |   - [1]
+ | ...
+SELECT abs(-1.5);
+ | ---
+ | - metadata:
+ |   - name: abs(-1.5)
+ |     type: number
+ |   rows:
+ |   - [1.5]
+ | ...
+SELECT abs(true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to number'
+ | ...
+SELECT abs('abc');
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert abc to number'
+ | ...
+SELECT abs(X'3334');
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert varbinary to number'
+ | ...
+
 -- Function char().
 SELECT char();
  | ---
@@ -45,6 +93,36 @@ SELECT char(1, 2);
  |   - ["\x01\x02"]
  | ...
 
+SELECT char(NULL);
+ | ---
+ | - metadata:
+ |   - name: char(NULL)
+ |     type: string
+ |   rows:
+ |   - [null]
+ | ...
+SELECT char(1);
+ | ---
+ | - metadata:
+ |   - name: char(1)
+ |     type: string
+ |   rows:
+ |   - ["\x01"]
+ | ...
+SELECT char(-1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to unsigned'
+ | ...
+SELECT char(-1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to unsigned'
+ | ...
+-- SELECT char(true);
+-- SELECT char('abc');
+-- SELECT char(X'3334');
+
 -- Function character_length().
 SELECT character_length();
  | ---
@@ -65,6 +143,51 @@ SELECT character_length('1', '2');
  | - 'Wrong number of arguments is passed to CHARACTER_LENGTH(): expected 1, got 2'
  | ...
 
+SELECT character_length(NULL);
+ | ---
+ | - metadata:
+ |   - name: character_length(NULL)
+ |     type: integer
+ |   rows:
+ |   - [null]
+ | ...
+SELECT character_length(1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert 1 to string'
+ | ...
+SELECT character_length(-1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to string'
+ | ...
+SELECT character_length(-1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to string'
+ | ...
+SELECT character_length(true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to string'
+ | ...
+SELECT character_length('abc');
+ | ---
+ | - metadata:
+ |   - name: character_length('abc')
+ |     type: integer
+ |   rows:
+ |   - [3]
+ | ...
+SELECT character_length(X'3334');
+ | ---
+ | - metadata:
+ |   - name: character_length(X'3334')
+ |     type: integer
+ |   rows:
+ |   - [2]
+ | ...
+
 -- Function char_length().
 SELECT char_length();
  | ---
@@ -85,6 +208,51 @@ SELECT char_length('1', '2');
  | - 'Wrong number of arguments is passed to CHAR_LENGTH(): expected 1, got 2'
  | ...
 
+SELECT char_length(NULL);
+ | ---
+ | - metadata:
+ |   - name: char_length(NULL)
+ |     type: integer
+ |   rows:
+ |   - [null]
+ | ...
+SELECT char_length(1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert 1 to string'
+ | ...
+SELECT char_length(-1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to string'
+ | ...
+SELECT char_length(-1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to string'
+ | ...
+SELECT char_length(true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to string'
+ | ...
+SELECT char_length('abc');
+ | ---
+ | - metadata:
+ |   - name: char_length('abc')
+ |     type: integer
+ |   rows:
+ |   - [3]
+ | ...
+SELECT char_length(X'3334');
+ | ---
+ | - metadata:
+ |   - name: char_length(X'3334')
+ |     type: integer
+ |   rows:
+ |   - [2]
+ | ...
+
 -- Function coalesce().
 SELECT coalesce();
  | ---
@@ -115,6 +283,63 @@ SELECT coalesce('1', '2', '3');
  |   - ['1']
  | ...
 
+SELECT coalesce(NULL, NULL);
+ | ---
+ | - metadata:
+ |   - name: coalesce(NULL, NULL)
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT coalesce(1, 1);
+ | ---
+ | - metadata:
+ |   - name: coalesce(1, 1)
+ |     type: scalar
+ |   rows:
+ |   - [1]
+ | ...
+SELECT coalesce(-1, -1);
+ | ---
+ | - metadata:
+ |   - name: coalesce(-1, -1)
+ |     type: scalar
+ |   rows:
+ |   - [-1]
+ | ...
+SELECT coalesce(-1.5, -1.5);
+ | ---
+ | - metadata:
+ |   - name: coalesce(-1.5, -1.5)
+ |     type: scalar
+ |   rows:
+ |   - [-1.5]
+ | ...
+SELECT coalesce(true, true);
+ | ---
+ | - metadata:
+ |   - name: coalesce(true, true)
+ |     type: scalar
+ |   rows:
+ |   - [true]
+ | ...
+SELECT coalesce('abc', 'abc');
+ | ---
+ | - metadata:
+ |   - name: coalesce('abc', 'abc')
+ |     type: scalar
+ |   rows:
+ |   - ['abc']
+ | ...
+SELECT coalesce(X'3334', X'3334');
+ | ---
+ | - metadata:
+ |   - name: coalesce(X'3334', X'3334')
+ |     type: scalar
+ |   rows:
+ |   - ['34']
+ | ...
+
 -- Function greatest().
 SELECT greatest();
  | ---
@@ -137,6 +362,63 @@ SELECT greatest('1', '2');
  |   - ['2']
  | ...
 
+SELECT greatest(NULL, NULL);
+ | ---
+ | - metadata:
+ |   - name: greatest(NULL, NULL)
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT greatest(1, 1);
+ | ---
+ | - metadata:
+ |   - name: greatest(1, 1)
+ |     type: scalar
+ |   rows:
+ |   - [1]
+ | ...
+SELECT greatest(-1, -1);
+ | ---
+ | - metadata:
+ |   - name: greatest(-1, -1)
+ |     type: scalar
+ |   rows:
+ |   - [-1]
+ | ...
+SELECT greatest(-1.5, -1.5);
+ | ---
+ | - metadata:
+ |   - name: greatest(-1.5, -1.5)
+ |     type: scalar
+ |   rows:
+ |   - [-1.5]
+ | ...
+SELECT greatest(true, true);
+ | ---
+ | - metadata:
+ |   - name: greatest(true, true)
+ |     type: scalar
+ |   rows:
+ |   - [true]
+ | ...
+SELECT greatest('abc', 'abc');
+ | ---
+ | - metadata:
+ |   - name: greatest('abc', 'abc')
+ |     type: scalar
+ |   rows:
+ |   - ['abc']
+ | ...
+SELECT greatest(X'3334', X'3334');
+ | ---
+ | - metadata:
+ |   - name: greatest(X'3334', X'3334')
+ |     type: scalar
+ |   rows:
+ |   - ['34']
+ | ...
+
 -- Function hex().
 SELECT hex();
  | ---
@@ -157,6 +439,51 @@ SELECT hex(X'33', X'33');
  | - 'Wrong number of arguments is passed to HEX(): expected 1, got 2'
  | ...
 
+SELECT hex(NULL);
+ | ---
+ | - metadata:
+ |   - name: hex(NULL)
+ |     type: string
+ |   rows:
+ |   - ['']
+ | ...
+SELECT hex(1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert 1 to string'
+ | ...
+SELECT hex(-1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to string'
+ | ...
+SELECT hex(-1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to string'
+ | ...
+SELECT hex(true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to string'
+ | ...
+SELECT hex('abc');
+ | ---
+ | - metadata:
+ |   - name: hex('abc')
+ |     type: string
+ |   rows:
+ |   - ['616263']
+ | ...
+SELECT hex(X'3334');
+ | ---
+ | - metadata:
+ |   - name: hex(X'3334')
+ |     type: string
+ |   rows:
+ |   - ['3334']
+ | ...
+
 -- Function ifnull
 SELECT ifnull();
  | ---
@@ -182,46 +509,205 @@ SELECT ifnull(1, 2, 3);
  | - 'Wrong number of arguments is passed to IFNULL(): expected 2, got 3'
  | ...
 
--- Function least().
-SELECT least();
+SELECT ifnull(NULL, NULL);
  | ---
- | - null
- | - 'Wrong number of arguments is passed to LEAST(): expected from 2 to 127, got 0'
+ | - metadata:
+ |   - name: ifnull(NULL, NULL)
+ |     type: integer
+ |   rows:
+ |   - [null]
  | ...
-SELECT least('1');
+SELECT ifnull(1, 1);
  | ---
- | - null
- | - 'Wrong number of arguments is passed to LEAST(): expected from 2 to 127, got 1'
+ | - metadata:
+ |   - name: ifnull(1, 1)
+ |     type: integer
+ |   rows:
+ |   - [1]
  | ...
-SELECT least('1', '2');
+SELECT ifnull(-1, -1);
  | ---
  | - metadata:
- |   - name: least('1', '2')
- |     type: scalar
+ |   - name: ifnull(-1, -1)
+ |     type: integer
  |   rows:
- |   - ['1']
+ |   - [-1]
  | ...
-
--- Function length().
-SELECT length();
+SELECT ifnull(-1.5, -1.5);
  | ---
- | - null
- | - 'Wrong number of arguments is passed to LENGTH(): expected 1, got 0'
+ | - metadata:
+ |   - name: ifnull(-1.5, -1.5)
+ |     type: integer
+ |   rows:
+ |   - [-1.5]
  | ...
-SELECT length('1');
+SELECT ifnull(true, true);
  | ---
  | - metadata:
- |   - name: length('1')
+ |   - name: ifnull(true, true)
  |     type: integer
  |   rows:
- |   - [1]
+ |   - [true]
  | ...
-SELECT length('1', '2');
+SELECT ifnull('abc', 'abc');
  | ---
- | - null
+ | - metadata:
+ |   - name: ifnull('abc', 'abc')
+ |     type: integer
+ |   rows:
+ |   - ['abc']
+ | ...
+SELECT ifnull(X'3334', X'3334');
+ | ---
+ | - metadata:
+ |   - name: ifnull(X'3334', X'3334')
+ |     type: integer
+ |   rows:
+ |   - ['34']
+ | ...
+
+-- Function least().
+SELECT least();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LEAST(): expected from 2 to 127, got 0'
+ | ...
+SELECT least('1');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LEAST(): expected from 2 to 127, got 1'
+ | ...
+SELECT least('1', '2');
+ | ---
+ | - metadata:
+ |   - name: least('1', '2')
+ |     type: scalar
+ |   rows:
+ |   - ['1']
+ | ...
+
+SELECT least(NULL, NULL);
+ | ---
+ | - metadata:
+ |   - name: least(NULL, NULL)
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT least(1, 1);
+ | ---
+ | - metadata:
+ |   - name: least(1, 1)
+ |     type: scalar
+ |   rows:
+ |   - [1]
+ | ...
+SELECT least(-1, -1);
+ | ---
+ | - metadata:
+ |   - name: least(-1, -1)
+ |     type: scalar
+ |   rows:
+ |   - [-1]
+ | ...
+SELECT least(-1.5, -1.5);
+ | ---
+ | - metadata:
+ |   - name: least(-1.5, -1.5)
+ |     type: scalar
+ |   rows:
+ |   - [-1.5]
+ | ...
+SELECT least(true, true);
+ | ---
+ | - metadata:
+ |   - name: least(true, true)
+ |     type: scalar
+ |   rows:
+ |   - [true]
+ | ...
+SELECT least('abc', 'abc');
+ | ---
+ | - metadata:
+ |   - name: least('abc', 'abc')
+ |     type: scalar
+ |   rows:
+ |   - ['abc']
+ | ...
+SELECT least(X'3334', X'3334');
+ | ---
+ | - metadata:
+ |   - name: least(X'3334', X'3334')
+ |     type: scalar
+ |   rows:
+ |   - ['34']
+ | ...
+
+-- Function length().
+SELECT length();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to LENGTH(): expected 1, got 0'
+ | ...
+SELECT length('1');
+ | ---
+ | - metadata:
+ |   - name: length('1')
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT length('1', '2');
+ | ---
+ | - null
  | - 'Wrong number of arguments is passed to LENGTH(): expected 1, got 2'
  | ...
 
+SELECT length(NULL);
+ | ---
+ | - metadata:
+ |   - name: length(NULL)
+ |     type: integer
+ |   rows:
+ |   - [null]
+ | ...
+SELECT length(1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert 1 to string'
+ | ...
+SELECT length(-1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to string'
+ | ...
+SELECT length(-1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to string'
+ | ...
+SELECT length(true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to string'
+ | ...
+SELECT length('abc');
+ | ---
+ | - metadata:
+ |   - name: length('abc')
+ |     type: integer
+ |   rows:
+ |   - [3]
+ | ...
+SELECT length(X'3334');
+ | ---
+ | - metadata:
+ |   - name: length(X'3334')
+ |     type: integer
+ |   rows:
+ |   - [2]
+ | ...
+
 -- Function likelihood
 SELECT likelihood();
  | ---
@@ -247,6 +733,49 @@ SELECT likelihood(1, 0.5, 3);
  | - 'Wrong number of arguments is passed to LIKELIHOOD(): expected 2, got 3'
  | ...
 
+SELECT likelihood(NULL, NULL);
+ | ---
+ | - null
+ | - Illegal parameters, second argument to likelihood() must be a constant between 0.0
+ |   and 1.0
+ | ...
+SELECT likelihood(1, 1);
+ | ---
+ | - null
+ | - Illegal parameters, second argument to likelihood() must be a constant between 0.0
+ |   and 1.0
+ | ...
+SELECT likelihood(-1, -1);
+ | ---
+ | - null
+ | - Illegal parameters, second argument to likelihood() must be a constant between 0.0
+ |   and 1.0
+ | ...
+SELECT likelihood(-1.5, -1.5);
+ | ---
+ | - null
+ | - Illegal parameters, second argument to likelihood() must be a constant between 0.0
+ |   and 1.0
+ | ...
+SELECT likelihood(true, true);
+ | ---
+ | - null
+ | - Illegal parameters, second argument to likelihood() must be a constant between 0.0
+ |   and 1.0
+ | ...
+SELECT likelihood('abc', 'abc');
+ | ---
+ | - null
+ | - Illegal parameters, second argument to likelihood() must be a constant between 0.0
+ |   and 1.0
+ | ...
+SELECT likelihood(X'3334', X'3334');
+ | ---
+ | - null
+ | - Illegal parameters, second argument to likelihood() must be a constant between 0.0
+ |   and 1.0
+ | ...
+
 -- Function likely
 SELECT likely();
  | ---
@@ -267,6 +796,63 @@ SELECT likely(1, 2);
  | - 'Wrong number of arguments is passed to LIKELY(): expected 1, got 2'
  | ...
 
+SELECT likely(NULL);
+ | ---
+ | - metadata:
+ |   - name: likely(NULL)
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT likely(1);
+ | ---
+ | - metadata:
+ |   - name: likely(1)
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT likely(-1);
+ | ---
+ | - metadata:
+ |   - name: likely(-1)
+ |     type: integer
+ |   rows:
+ |   - [-1]
+ | ...
+SELECT likely(-1.5);
+ | ---
+ | - metadata:
+ |   - name: likely(-1.5)
+ |     type: double
+ |   rows:
+ |   - [-1.5]
+ | ...
+SELECT likely(true);
+ | ---
+ | - metadata:
+ |   - name: likely(true)
+ |     type: boolean
+ |   rows:
+ |   - [true]
+ | ...
+SELECT likely('abc');
+ | ---
+ | - metadata:
+ |   - name: likely('abc')
+ |     type: string
+ |   rows:
+ |   - ['abc']
+ | ...
+SELECT likely(X'3334');
+ | ---
+ | - metadata:
+ |   - name: likely(X'3334')
+ |     type: varbinary
+ |   rows:
+ |   - ['34']
+ | ...
+
 -- Function lower
 SELECT lower();
  | ---
@@ -287,6 +873,51 @@ SELECT lower('a', 2);
  | - 'Wrong number of arguments is passed to LOWER(): expected 1, got 2'
  | ...
 
+SELECT lower(NULL);
+ | ---
+ | - metadata:
+ |   - name: lower(NULL)
+ |     type: string
+ |   rows:
+ |   - [null]
+ | ...
+SELECT lower(1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert 1 to string'
+ | ...
+SELECT lower(-1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to string'
+ | ...
+SELECT lower(-1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to string'
+ | ...
+SELECT lower(true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to string'
+ | ...
+SELECT lower('abc');
+ | ---
+ | - metadata:
+ |   - name: lower('abc')
+ |     type: string
+ |   rows:
+ |   - ['abc']
+ | ...
+SELECT lower(X'3334');
+ | ---
+ | - metadata:
+ |   - name: lower(X'3334')
+ |     type: string
+ |   rows:
+ |   - ['34']
+ | ...
+
 -- Function nullif
 SELECT nullif();
  | ---
@@ -312,6 +943,63 @@ SELECT nullif(1, 2, 3);
  | - 'Wrong number of arguments is passed to NULLIF(): expected 2, got 3'
  | ...
 
+SELECT nullif(NULL, NULL);
+ | ---
+ | - metadata:
+ |   - name: nullif(NULL, NULL)
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT nullif(1, 1);
+ | ---
+ | - metadata:
+ |   - name: nullif(1, 1)
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT nullif(-1, -1);
+ | ---
+ | - metadata:
+ |   - name: nullif(-1, -1)
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT nullif(-1.5, -1.5);
+ | ---
+ | - metadata:
+ |   - name: nullif(-1.5, -1.5)
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT nullif(true, true);
+ | ---
+ | - metadata:
+ |   - name: nullif(true, true)
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT nullif('abc', 'abc');
+ | ---
+ | - metadata:
+ |   - name: nullif('abc', 'abc')
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT nullif(X'3334', X'3334');
+ | ---
+ | - metadata:
+ |   - name: nullif(X'3334', X'3334')
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+
 -- Function position
 SELECT position();
  | ---
@@ -337,366 +1025,1075 @@ SELECT position('12345', '2', 3);
  | - 'Wrong number of arguments is passed to POSITION(): expected 2, got 3'
  | ...
 
--- Function printf
-SELECT printf();
+SELECT position(NULL, NULL);
+ | ---
+ | - metadata:
+ |   - name: position(NULL, NULL)
+ |     type: integer
+ |   rows:
+ |   - [null]
+ | ...
+SELECT position(1, 1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert 1 to string'
+ | ...
+SELECT position(-1, -1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to string'
+ | ...
+SELECT position(-1.5, -1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to string'
+ | ...
+SELECT position(true, true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to string'
+ | ...
+SELECT position('abc', 'abc');
+ | ---
+ | - metadata:
+ |   - name: position('abc', 'abc')
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT position(X'3334', X'3334');
+ | ---
+ | - metadata:
+ |   - name: position(X'3334', X'3334')
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+
+-- Function printf
+SELECT printf();
+ | ---
+ | - metadata:
+ |   - name: printf()
+ |     type: string
+ |   rows:
+ |   - [null]
+ | ...
+SELECT printf('1');
+ | ---
+ | - metadata:
+ |   - name: printf('1')
+ |     type: string
+ |   rows:
+ |   - ['1']
+ | ...
+SELECT printf('1', 2);
+ | ---
+ | - metadata:
+ |   - name: printf('1', 2)
+ |     type: string
+ |   rows:
+ |   - ['1']
+ | ...
+SELECT printf('1', 2, 3);
+ | ---
+ | - metadata:
+ |   - name: printf('1', 2, 3)
+ |     type: string
+ |   rows:
+ |   - ['1']
+ | ...
+
+SELECT printf(NULL, NULL);
+ | ---
+ | - metadata:
+ |   - name: printf(NULL, NULL)
+ |     type: string
+ |   rows:
+ |   - [null]
+ | ...
+SELECT printf(1, 1);
+ | ---
+ | - metadata:
+ |   - name: printf(1, 1)
+ |     type: string
+ |   rows:
+ |   - ['1']
+ | ...
+SELECT printf(-1, -1);
+ | ---
+ | - metadata:
+ |   - name: printf(-1, -1)
+ |     type: string
+ |   rows:
+ |   - ['-1']
+ | ...
+SELECT printf(-1.5, -1.5);
+ | ---
+ | - metadata:
+ |   - name: printf(-1.5, -1.5)
+ |     type: string
+ |   rows:
+ |   - ['-1.5']
+ | ...
+SELECT printf(true, true);
+ | ---
+ | - metadata:
+ |   - name: printf(true, true)
+ |     type: string
+ |   rows:
+ |   - ['TRUE']
+ | ...
+SELECT printf('abc', 'abc');
+ | ---
+ | - metadata:
+ |   - name: printf('abc', 'abc')
+ |     type: string
+ |   rows:
+ |   - ['abc']
+ | ...
+SELECT printf(X'3334', X'3334');
+ | ---
+ | - metadata:
+ |   - name: printf(X'3334', X'3334')
+ |     type: string
+ |   rows:
+ |   - ['34']
+ | ...
+
+-- Function quote
+SELECT quote();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to QUOTE(): expected 1, got 0'
+ | ...
+SELECT quote('1');
+ | ---
+ | - metadata:
+ |   - name: quote('1')
+ |     type: string
+ |   rows:
+ |   - ['''1''']
+ | ...
+SELECT quote('1', 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to QUOTE(): expected 1, got 2'
+ | ...
+
+SELECT quote(NULL);
+ | ---
+ | - metadata:
+ |   - name: quote(NULL)
+ |     type: string
+ |   rows:
+ |   - ['NULL']
+ | ...
+SELECT quote(1);
+ | ---
+ | - metadata:
+ |   - name: quote(1)
+ |     type: string
+ |   rows:
+ |   - [1]
+ | ...
+SELECT quote(-1);
+ | ---
+ | - metadata:
+ |   - name: quote(-1)
+ |     type: string
+ |   rows:
+ |   - [-1]
+ | ...
+SELECT quote(-1.5);
+ | ---
+ | - metadata:
+ |   - name: quote(-1.5)
+ |     type: string
+ |   rows:
+ |   - ['-1.5']
+ | ...
+SELECT quote(true);
+ | ---
+ | - metadata:
+ |   - name: quote(true)
+ |     type: string
+ |   rows:
+ |   - ['TRUE']
+ | ...
+SELECT quote('abc');
+ | ---
+ | - metadata:
+ |   - name: quote('abc')
+ |     type: string
+ |   rows:
+ |   - ['''abc''']
+ | ...
+SELECT quote(X'3334');
+ | ---
+ | - metadata:
+ |   - name: quote(X'3334')
+ |     type: string
+ |   rows:
+ |   - ['X''3334''']
+ | ...
+
+-- Function random
+SELECT typeof(random());
+ | ---
+ | - metadata:
+ |   - name: typeof(random())
+ |     type: string
+ |   rows:
+ |   - ['integer']
+ | ...
+SELECT typeof(random(1));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to RANDOM(): expected 0, got 1'
+ | ...
+
+-- Function randomblob
+SELECT typeof(randomblob());
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to RANDOMBLOB(): expected 1, got 0'
+ | ...
+SELECT typeof(randomblob(1));
+ | ---
+ | - metadata:
+ |   - name: typeof(randomblob(1))
+ |     type: string
+ |   rows:
+ |   - ['varbinary']
+ | ...
+SELECT typeof(randomblob(1, 2));
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to RANDOMBLOB(): expected 1, got 2'
+ | ...
+
+SELECT randomblob(NULL);
+ | ---
+ | - metadata:
+ |   - name: randomblob(NULL)
+ |     type: varbinary
+ |   rows:
+ |   - [null]
+ | ...
+SELECT typeof(randomblob(1));
+ | ---
+ | - metadata:
+ |   - name: typeof(randomblob(1))
+ |     type: string
+ |   rows:
+ |   - ['varbinary']
+ | ...
+SELECT typeof(randomblob(-1));
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to unsigned'
+ | ...
+SELECT typeof(randomblob(-1.5));
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to unsigned'
+ | ...
+SELECT typeof(randomblob(true));
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to unsigned'
+ | ...
+SELECT typeof(randomblob('abc'));
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert abc to unsigned'
+ | ...
+SELECT typeof(randomblob(X'3334'));
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert varbinary to unsigned'
+ | ...
+
+-- Function replace
+SELECT replace();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 0'
+ | ...
+SELECT replace('12345');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 1'
+ | ...
+SELECT replace('12345', '2');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 2'
+ | ...
+SELECT replace('12345', '2', '3');
+ | ---
+ | - metadata:
+ |   - name: replace('12345', '2', '3')
+ |     type: string
+ |   rows:
+ |   - ['13345']
+ | ...
+SELECT replace('12345', '2', '3', 4);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 4'
+ | ...
+
+SELECT replace(NULL, NULL, NULL);
+ | ---
+ | - metadata:
+ |   - name: replace(NULL, NULL, NULL)
+ |     type: string
+ |   rows:
+ |   - [null]
+ | ...
+SELECT replace(1, 1, 1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert 1 to string'
+ | ...
+SELECT replace(-1, -1, -1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to string'
+ | ...
+SELECT replace(-1.5, -1.5, -1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to string'
+ | ...
+SELECT replace(true, true, true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to string'
+ | ...
+SELECT replace('abc', 'abc', 'abc');
+ | ---
+ | - metadata:
+ |   - name: replace('abc', 'abc', 'abc')
+ |     type: string
+ |   rows:
+ |   - ['abc']
+ | ...
+SELECT replace(X'3334', X'3334', X'3334');
+ | ---
+ | - metadata:
+ |   - name: replace(X'3334', X'3334', X'3334')
+ |     type: string
+ |   rows:
+ |   - ['34']
+ | ...
+
+-- Function round
+SELECT round();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 0'
+ | ...
+SELECT round(1.1245);
+ | ---
+ | - metadata:
+ |   - name: round(1.1245)
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT round(1.1245, 2);
+ | ---
+ | - metadata:
+ |   - name: round(1.1245, 2)
+ |     type: integer
+ |   rows:
+ |   - [1.12]
+ | ...
+SELECT round(1.1245, 2, 3);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 3'
+ | ...
+
+SELECT round(NULL, NULL);
+ | ---
+ | - metadata:
+ |   - name: round(NULL, NULL)
+ |     type: integer
+ |   rows:
+ |   - [null]
+ | ...
+SELECT round(1, 1);
+ | ---
+ | - metadata:
+ |   - name: round(1, 1)
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT round(-1, -1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to unsigned'
+ | ...
+SELECT round(-1.5, -1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to unsigned'
+ | ...
+SELECT round(true, true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to double'
+ | ...
+SELECT round('abc', 'abc');
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert abc to double'
+ | ...
+SELECT round(X'3334', X'3334');
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert varbinary to double'
+ | ...
+
+-- Function row_count
+SELECT row_count();
+ | ---
+ | - metadata:
+ |   - name: row_count()
+ |     type: integer
+ |   rows:
+ |   - [0]
+ | ...
+SELECT row_count(1);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to ROW_COUNT(): expected 0, got 1'
+ | ...
+
+-- Function soundex
+SELECT soundex();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SOUNDEX(): expected 1, got 0'
+ | ...
+SELECT soundex(1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert 1 to string'
+ | ...
+SELECT soundex(1, 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SOUNDEX(): expected 1, got 2'
+ | ...
+
+SELECT soundex(NULL);
+ | ---
+ | - metadata:
+ |   - name: soundex(NULL)
+ |     type: string
+ |   rows:
+ |   - ['?000']
+ | ...
+SELECT soundex(1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert 1 to string'
+ | ...
+SELECT soundex(-1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to string'
+ | ...
+SELECT soundex(-1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to string'
+ | ...
+SELECT soundex(true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to string'
+ | ...
+SELECT soundex('abc');
+ | ---
+ | - metadata:
+ |   - name: soundex('abc')
+ |     type: string
+ |   rows:
+ |   - ['A120']
+ | ...
+SELECT soundex(X'3334');
+ | ---
+ | - metadata:
+ |   - name: soundex(X'3334')
+ |     type: string
+ |   rows:
+ |   - ['?000']
+ | ...
+
+-- Function substr
+SELECT substr();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 0'
+ | ...
+SELECT substr('12345');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 1'
+ | ...
+SELECT substr('12345', 2);
+ | ---
+ | - metadata:
+ |   - name: substr('12345', 2)
+ |     type: string
+ |   rows:
+ |   - ['2345']
+ | ...
+SELECT substr('12345', 2, 3);
+ | ---
+ | - metadata:
+ |   - name: substr('12345', 2, 3)
+ |     type: string
+ |   rows:
+ |   - ['234']
+ | ...
+SELECT substr('12345', 2, 3, 4);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 4'
+ | ...
+
+SELECT substr(NULL, NULL);
+ | ---
+ | - metadata:
+ |   - name: substr(NULL, NULL)
+ |     type: string
+ |   rows:
+ |   - [null]
+ | ...
+SELECT substr(1, 1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert 1 to string'
+ | ...
+SELECT substr(-1, -1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to string'
+ | ...
+SELECT substr(-1.5, -1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to string'
+ | ...
+SELECT substr(true, true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to string'
+ | ...
+SELECT substr('abc', 'abc');
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert abc to integer'
+ | ...
+SELECT substr(X'3334', X'3334');
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert varbinary to integer'
+ | ...
+
+-- Function typeof
+SELECT typeof();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to TYPEOF(): expected 1, got 0'
+ | ...
+SELECT typeof(1);
+ | ---
+ | - metadata:
+ |   - name: typeof(1)
+ |     type: string
+ |   rows:
+ |   - ['integer']
+ | ...
+SELECT typeof(1, 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to TYPEOF(): expected 1, got 2'
+ | ...
+
+SELECT typeof(NULL);
+ | ---
+ | - metadata:
+ |   - name: typeof(NULL)
+ |     type: string
+ |   rows:
+ |   - ['boolean']
+ | ...
+SELECT typeof(1);
+ | ---
+ | - metadata:
+ |   - name: typeof(1)
+ |     type: string
+ |   rows:
+ |   - ['integer']
+ | ...
+SELECT typeof(-1);
+ | ---
+ | - metadata:
+ |   - name: typeof(-1)
+ |     type: string
+ |   rows:
+ |   - ['integer']
+ | ...
+SELECT typeof(-1.5);
+ | ---
+ | - metadata:
+ |   - name: typeof(-1.5)
+ |     type: string
+ |   rows:
+ |   - ['double']
+ | ...
+SELECT typeof(true);
+ | ---
+ | - metadata:
+ |   - name: typeof(true)
+ |     type: string
+ |   rows:
+ |   - ['boolean']
+ | ...
+SELECT typeof('abc');
+ | ---
+ | - metadata:
+ |   - name: typeof('abc')
+ |     type: string
+ |   rows:
+ |   - ['string']
+ | ...
+SELECT typeof(X'3334');
+ | ---
+ | - metadata:
+ |   - name: typeof(X'3334')
+ |     type: string
+ |   rows:
+ |   - ['varbinary']
+ | ...
+
+-- Function unicode
+SELECT unicode();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to UNICODE(): expected 1, got 0'
+ | ...
+SELECT unicode('1');
+ | ---
+ | - metadata:
+ |   - name: unicode('1')
+ |     type: string
+ |   rows:
+ |   - [49]
+ | ...
+SELECT unicode('1', '2');
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to UNICODE(): expected 1, got 2'
+ | ...
+
+SELECT unicode(NULL);
+ | ---
+ | - metadata:
+ |   - name: unicode(NULL)
+ |     type: string
+ |   rows:
+ |   - [null]
+ | ...
+SELECT unicode(1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert 1 to string'
+ | ...
+SELECT unicode(-1);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1 to string'
+ | ...
+SELECT unicode(-1.5);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert -1.5 to string'
+ | ...
+SELECT unicode(true);
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to string'
+ | ...
+SELECT unicode('abc');
+ | ---
+ | - metadata:
+ |   - name: unicode('abc')
+ |     type: string
+ |   rows:
+ |   - [97]
+ | ...
+SELECT unicode(X'3334');
+ | ---
+ | - metadata:
+ |   - name: unicode(X'3334')
+ |     type: string
+ |   rows:
+ |   - [51]
+ | ...
+
+-- Function unlikely
+SELECT unlikely();
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to UNLIKELY(): expected 1, got 0'
+ | ...
+SELECT unlikely(1);
+ | ---
+ | - metadata:
+ |   - name: unlikely(1)
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT unlikely(1, 2);
+ | ---
+ | - null
+ | - 'Wrong number of arguments is passed to UNLIKELY(): expected 1, got 2'
+ | ...
+
+SELECT unlikely(NULL);
+ | ---
+ | - metadata:
+ |   - name: unlikely(NULL)
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT unlikely(1);
+ | ---
+ | - metadata:
+ |   - name: unlikely(1)
+ |     type: integer
+ |   rows:
+ |   - [1]
+ | ...
+SELECT unlikely(-1);
  | ---
  | - metadata:
- |   - name: printf()
- |     type: string
+ |   - name: unlikely(-1)
+ |     type: integer
  |   rows:
- |   - [null]
+ |   - [-1]
  | ...
-SELECT printf('1');
+SELECT unlikely(-1.5);
  | ---
  | - metadata:
- |   - name: printf('1')
- |     type: string
+ |   - name: unlikely(-1.5)
+ |     type: double
  |   rows:
- |   - ['1']
+ |   - [-1.5]
  | ...
-SELECT printf('1', 2);
+SELECT unlikely(true);
  | ---
  | - metadata:
- |   - name: printf('1', 2)
- |     type: string
+ |   - name: unlikely(true)
+ |     type: boolean
  |   rows:
- |   - ['1']
+ |   - [true]
  | ...
-SELECT printf('1', 2, 3);
+SELECT unlikely('abc');
  | ---
  | - metadata:
- |   - name: printf('1', 2, 3)
+ |   - name: unlikely('abc')
  |     type: string
  |   rows:
- |   - ['1']
+ |   - ['abc']
  | ...
-
--- Function quote
-SELECT quote();
- | ---
- | - null
- | - 'Wrong number of arguments is passed to QUOTE(): expected 1, got 0'
- | ...
-SELECT quote('1');
+SELECT unlikely(X'3334');
  | ---
  | - metadata:
- |   - name: quote('1')
- |     type: string
+ |   - name: unlikely(X'3334')
+ |     type: varbinary
  |   rows:
- |   - ['''1''']
+ |   - ['34']
  | ...
-SELECT quote('1', 2);
+
+-- Function upper
+SELECT upper();
  | ---
  | - null
- | - 'Wrong number of arguments is passed to QUOTE(): expected 1, got 2'
+ | - 'Wrong number of arguments is passed to UPPER(): expected 1, got 0'
  | ...
-
--- Function random
-SELECT typeof(random());
+SELECT upper('a');
  | ---
  | - metadata:
- |   - name: typeof(random())
+ |   - name: upper('a')
  |     type: string
  |   rows:
- |   - ['integer']
+ |   - ['A']
  | ...
-SELECT typeof(random(1));
+SELECT upper('a', 2);
  | ---
  | - null
- | - 'Wrong number of arguments is passed to RANDOM(): expected 0, got 1'
+ | - 'Wrong number of arguments is passed to UPPER(): expected 1, got 2'
  | ...
 
--- Function randomblob
-SELECT typeof(randomblob());
- | ---
- | - null
- | - 'Wrong number of arguments is passed to RANDOMBLOB(): expected 1, got 0'
- | ...
-SELECT typeof(randomblob(1));
+SELECT upper(NULL);
  | ---
  | - metadata:
- |   - name: typeof(randomblob(1))
+ |   - name: upper(NULL)
  |     type: string
  |   rows:
- |   - ['varbinary']
+ |   - [null]
  | ...
-SELECT typeof(randomblob(1, 2));
+SELECT upper(1);
  | ---
  | - null
- | - 'Wrong number of arguments is passed to RANDOMBLOB(): expected 1, got 2'
+ | - 'Type mismatch: can not convert 1 to string'
  | ...
-
--- Function replace
-SELECT replace();
+SELECT upper(-1);
  | ---
  | - null
- | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 0'
+ | - 'Type mismatch: can not convert -1 to string'
  | ...
-SELECT replace('12345');
+SELECT upper(-1.5);
  | ---
  | - null
- | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 1'
+ | - 'Type mismatch: can not convert -1.5 to string'
  | ...
-SELECT replace('12345', '2');
+SELECT upper(true);
  | ---
  | - null
- | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 2'
+ | - 'Type mismatch: can not convert TRUE to string'
  | ...
-SELECT replace('12345', '2', '3');
+SELECT upper('abc');
  | ---
  | - metadata:
- |   - name: replace('12345', '2', '3')
+ |   - name: upper('abc')
  |     type: string
  |   rows:
- |   - ['13345']
- | ...
-SELECT replace('12345', '2', '3', 4);
- | ---
- | - null
- | - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 4'
- | ...
-
--- Function round
-SELECT round();
- | ---
- | - null
- | - 'Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 0'
- | ...
-SELECT round(1.1245);
- | ---
- | - metadata:
- |   - name: round(1.1245)
- |     type: integer
- |   rows:
- |   - [1]
+ |   - ['ABC']
  | ...
-SELECT round(1.1245, 2);
+SELECT upper(X'3334');
  | ---
  | - metadata:
- |   - name: round(1.1245, 2)
- |     type: integer
+ |   - name: upper(X'3334')
+ |     type: string
  |   rows:
- |   - [1.12]
- | ...
-SELECT round(1.1245, 2, 3);
- | ---
- | - null
- | - 'Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 3'
+ |   - ['34']
  | ...
 
--- Function row_count
-SELECT row_count();
+-- Function version
+SELECT typeof(version());
  | ---
  | - metadata:
- |   - name: row_count()
- |     type: integer
+ |   - name: typeof(version())
+ |     type: string
  |   rows:
- |   - [0]
+ |   - ['string']
  | ...
-SELECT row_count(1);
+SELECT typeof(version(1));
  | ---
  | - null
- | - 'Wrong number of arguments is passed to ROW_COUNT(): expected 0, got 1'
+ | - 'Wrong number of arguments is passed to VERSION(): expected 0, got 1'
  | ...
 
--- Function soundex
-SELECT soundex();
+-- Function zeroblob
+SELECT zeroblob();
  | ---
  | - null
- | - 'Wrong number of arguments is passed to SOUNDEX(): expected 1, got 0'
+ | - 'Wrong number of arguments is passed to ZEROBLOB(): expected 1, got 0'
  | ...
-SELECT soundex(1);
+SELECT zeroblob(1);
  | ---
  | - metadata:
- |   - name: soundex(1)
- |     type: string
+ |   - name: zeroblob(1)
+ |     type: varbinary
  |   rows:
- |   - ['?000']
+ |   - ["\0"]
  | ...
-SELECT soundex(1, 2);
+SELECT zeroblob(1, 2);
  | ---
  | - null
- | - 'Wrong number of arguments is passed to SOUNDEX(): expected 1, got 2'
+ | - 'Wrong number of arguments is passed to ZEROBLOB(): expected 1, got 2'
  | ...
 
--- Function substr
-SELECT substr();
- | ---
- | - null
- | - 'Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 0'
- | ...
-SELECT substr('12345');
- | ---
- | - null
- | - 'Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 1'
- | ...
-SELECT substr('12345', 2);
+SELECT zeroblob(NULL);
  | ---
  | - metadata:
- |   - name: substr('12345', 2)
- |     type: string
+ |   - name: zeroblob(NULL)
+ |     type: varbinary
  |   rows:
- |   - ['2345']
+ |   - ['']
  | ...
-SELECT substr('12345', 2, 3);
+SELECT zeroblob(1);
  | ---
  | - metadata:
- |   - name: substr('12345', 2, 3)
- |     type: string
+ |   - name: zeroblob(1)
+ |     type: varbinary
  |   rows:
- |   - ['234']
+ |   - ["\0"]
  | ...
-SELECT substr('12345', 2, 3, 4);
+SELECT zeroblob(-1);
  | ---
  | - null
- | - 'Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 4'
+ | - 'Type mismatch: can not convert -1 to unsigned'
  | ...
-
--- Function typeof
-SELECT typeof();
+SELECT zeroblob(-1.5);
  | ---
  | - null
- | - 'Wrong number of arguments is passed to TYPEOF(): expected 1, got 0'
+ | - 'Type mismatch: can not convert -1.5 to unsigned'
  | ...
-SELECT typeof(1);
+SELECT zeroblob(true);
  | ---
- | - metadata:
- |   - name: typeof(1)
- |     type: string
- |   rows:
- |   - ['integer']
+ | - null
+ | - 'Type mismatch: can not convert TRUE to unsigned'
  | ...
-SELECT typeof(1, 2);
+SELECT zeroblob('abc');
  | ---
  | - null
- | - 'Wrong number of arguments is passed to TYPEOF(): expected 1, got 2'
+ | - 'Type mismatch: can not convert abc to unsigned'
+ | ...
+SELECT zeroblob(X'3334');
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert varbinary to unsigned'
  | ...
 
--- Function unicode
-SELECT unicode();
+-- Function avg
+SELECT avg() FROM (values(1), (2), (3));
  | ---
  | - null
- | - 'Wrong number of arguments is passed to UNICODE(): expected 1, got 0'
+ | - 'Wrong number of arguments is passed to AVG(): expected 1, got 0'
  | ...
-SELECT unicode('1');
+SELECT avg("_auto_field_") FROM (values(1), (2), (3));
  | ---
  | - metadata:
- |   - name: unicode('1')
- |     type: string
+ |   - name: avg("_auto_field_")
+ |     type: number
  |   rows:
- |   - [49]
+ |   - [2]
  | ...
-SELECT unicode('1', '2');
+SELECT avg("_auto_field_", 2) FROM (values(1), (2), (3));
  | ---
  | - null
- | - 'Wrong number of arguments is passed to UNICODE(): expected 1, got 2'
+ | - 'Wrong number of arguments is passed to AVG(): expected 1, got 2'
  | ...
 
--- Function unlikely
-SELECT unlikely();
+SELECT avg("_auto_field_") FROM (values(NULL), (NULL), (NULL));
  | ---
- | - null
- | - 'Wrong number of arguments is passed to UNLIKELY(): expected 1, got 0'
+ | - metadata:
+ |   - name: avg("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [null]
  | ...
-SELECT unlikely(1);
+SELECT avg("_auto_field_") FROM (values(1), (1), (1));
  | ---
  | - metadata:
- |   - name: unlikely(1)
- |     type: integer
+ |   - name: avg("_auto_field_")
+ |     type: number
  |   rows:
  |   - [1]
  | ...
-SELECT unlikely(1, 2);
- | ---
- | - null
- | - 'Wrong number of arguments is passed to UNLIKELY(): expected 1, got 2'
- | ...
-
--- Function upper
-SELECT upper();
+SELECT avg("_auto_field_") FROM (values(-1), (-1), (-1));
  | ---
- | - null
- | - 'Wrong number of arguments is passed to UPPER(): expected 1, got 0'
+ | - metadata:
+ |   - name: avg("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [-1]
  | ...
-SELECT upper('a');
+SELECT avg("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
  | ---
  | - metadata:
- |   - name: upper('a')
- |     type: string
+ |   - name: avg("_auto_field_")
+ |     type: number
  |   rows:
- |   - ['A']
+ |   - [-1.5]
  | ...
-SELECT upper('a', 2);
+SELECT avg("_auto_field_") FROM (values(true), (true), (true));
  | ---
  | - null
- | - 'Wrong number of arguments is passed to UPPER(): expected 1, got 2'
+ | - 'Type mismatch: can not convert TRUE to number'
  | ...
-
--- Function version
-SELECT typeof(version());
+SELECT avg("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
  | ---
- | - metadata:
- |   - name: typeof(version())
- |     type: string
- |   rows:
- |   - ['string']
+ | - null
+ | - 'Type mismatch: can not convert abc to number'
  | ...
-SELECT typeof(version(1));
+SELECT avg("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
  | ---
  | - null
- | - 'Wrong number of arguments is passed to VERSION(): expected 0, got 1'
+ | - 'Type mismatch: can not convert varbinary to number'
  | ...
 
--- Function zeroblob
-SELECT zeroblob();
+-- Function count
+SELECT count() FROM (values(1), (2), (3));
  | ---
- | - null
- | - 'Wrong number of arguments is passed to ZEROBLOB(): expected 1, got 0'
+ | - metadata:
+ |   - name: count()
+ |     type: integer
+ |   rows:
+ |   - [3]
  | ...
-SELECT zeroblob(1);
+SELECT count("_auto_field_") FROM (values(1), (2), (3));
  | ---
  | - metadata:
- |   - name: zeroblob(1)
- |     type: varbinary
+ |   - name: count("_auto_field_")
+ |     type: integer
  |   rows:
- |   - ["\0"]
+ |   - [3]
  | ...
-SELECT zeroblob(1, 2);
+SELECT count("_auto_field_", 2) FROM (values(1), (2), (3));
  | ---
  | - null
- | - 'Wrong number of arguments is passed to ZEROBLOB(): expected 1, got 2'
+ | - 'Wrong number of arguments is passed to COUNT(): expected from 0 to 1, got 2'
  | ...
 
--- Function avg
-SELECT avg() FROM (values(1), (2), (3));
+SELECT count("_auto_field_") FROM (values(NULL), (NULL), (NULL));
  | ---
- | - null
- | - 'Wrong number of arguments is passed to AVG(): expected 1, got 0'
+ | - metadata:
+ |   - name: count("_auto_field_")
+ |     type: integer
+ |   rows:
+ |   - [0]
  | ...
-SELECT avg("_auto_field_") FROM (values(1), (2), (3));
+SELECT count("_auto_field_") FROM (values(1), (1), (1));
  | ---
  | - metadata:
- |   - name: avg("_auto_field_")
- |     type: number
+ |   - name: count("_auto_field_")
+ |     type: integer
  |   rows:
- |   - [2]
+ |   - [3]
  | ...
-SELECT avg("_auto_field_", 2) FROM (values(1), (2), (3));
+SELECT count("_auto_field_") FROM (values(-1), (-1), (-1));
  | ---
- | - null
- | - 'Wrong number of arguments is passed to AVG(): expected 1, got 2'
+ | - metadata:
+ |   - name: count("_auto_field_")
+ |     type: integer
+ |   rows:
+ |   - [3]
  | ...
-
--- Function count
-SELECT count() FROM (values(1), (2), (3));
+SELECT count("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
  | ---
  | - metadata:
- |   - name: count()
+ |   - name: count("_auto_field_")
  |     type: integer
  |   rows:
  |   - [3]
  | ...
-SELECT count("_auto_field_") FROM (values(1), (2), (3));
+SELECT count("_auto_field_") FROM (values(true), (true), (true));
  | ---
  | - metadata:
  |   - name: count("_auto_field_")
@@ -704,10 +2101,21 @@ SELECT count("_auto_field_") FROM (values(1), (2), (3));
  |   rows:
  |   - [3]
  | ...
-SELECT count("_auto_field_", 2) FROM (values(1), (2), (3));
+SELECT count("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
  | ---
- | - null
- | - 'Wrong number of arguments is passed to COUNT(): expected from 0 to 1, got 2'
+ | - metadata:
+ |   - name: count("_auto_field_")
+ |     type: integer
+ |   rows:
+ |   - [3]
+ | ...
+SELECT count("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+ | ---
+ | - metadata:
+ |   - name: count("_auto_field_")
+ |     type: integer
+ |   rows:
+ |   - [3]
  | ...
 
 -- Function group_concat
@@ -740,6 +2148,63 @@ SELECT group_concat("_auto_field_", '2', '3') FROM (values('1'), ('2'), ('3'));
  |   3'
  | ...
 
+SELECT group_concat("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+ | ---
+ | - metadata:
+ |   - name: group_concat("_auto_field_")
+ |     type: string
+ |   rows:
+ |   - [null]
+ | ...
+SELECT group_concat("_auto_field_") FROM (values(1), (1), (1));
+ | ---
+ | - metadata:
+ |   - name: group_concat("_auto_field_")
+ |     type: string
+ |   rows:
+ |   - ['1,1,1']
+ | ...
+SELECT group_concat("_auto_field_") FROM (values(-1), (-1), (-1));
+ | ---
+ | - metadata:
+ |   - name: group_concat("_auto_field_")
+ |     type: string
+ |   rows:
+ |   - ['-1,-1,-1']
+ | ...
+SELECT group_concat("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+ | ---
+ | - metadata:
+ |   - name: group_concat("_auto_field_")
+ |     type: string
+ |   rows:
+ |   - ['-1.5,-1.5,-1.5']
+ | ...
+SELECT group_concat("_auto_field_") FROM (values(true), (true), (true));
+ | ---
+ | - metadata:
+ |   - name: group_concat("_auto_field_")
+ |     type: string
+ |   rows:
+ |   - ['TRUE,TRUE,TRUE']
+ | ...
+SELECT group_concat("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+ | ---
+ | - metadata:
+ |   - name: group_concat("_auto_field_")
+ |     type: string
+ |   rows:
+ |   - ['abc,abc,abc']
+ | ...
+SELECT group_concat("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+ | ---
+ | - metadata:
+ |   - name: group_concat("_auto_field_")
+ |     type: string
+ |   rows:
+ |   - ['34,34,34']
+ | ...
+
 -- Function max
 SELECT max() FROM (values(1), (2), (3));
  | ---
@@ -760,6 +2225,63 @@ SELECT max("_auto_field_", 2) FROM (values(1), (2), (3));
  | - 'Wrong number of arguments is passed to MAX(): expected 1, got 2'
  | ...
 
+SELECT max("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+ | ---
+ | - metadata:
+ |   - name: max("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT max("_auto_field_") FROM (values(1), (1), (1));
+ | ---
+ | - metadata:
+ |   - name: max("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [1]
+ | ...
+SELECT max("_auto_field_") FROM (values(-1), (-1), (-1));
+ | ---
+ | - metadata:
+ |   - name: max("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [-1]
+ | ...
+SELECT max("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+ | ---
+ | - metadata:
+ |   - name: max("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [-1.5]
+ | ...
+SELECT max("_auto_field_") FROM (values(true), (true), (true));
+ | ---
+ | - metadata:
+ |   - name: max("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [true]
+ | ...
+SELECT max("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+ | ---
+ | - metadata:
+ |   - name: max("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - ['abc']
+ | ...
+SELECT max("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+ | ---
+ | - metadata:
+ |   - name: max("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - ['34']
+ | ...
+
 -- Function min
 SELECT min() FROM (values(1), (2), (3));
  | ---
@@ -780,6 +2302,63 @@ SELECT min("_auto_field_", 2) FROM (values(1), (2), (3));
  | - 'Wrong number of arguments is passed to MIN(): expected 1, got 2'
  | ...
 
+SELECT min("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+ | ---
+ | - metadata:
+ |   - name: min("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [null]
+ | ...
+SELECT min("_auto_field_") FROM (values(1), (1), (1));
+ | ---
+ | - metadata:
+ |   - name: min("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [1]
+ | ...
+SELECT min("_auto_field_") FROM (values(-1), (-1), (-1));
+ | ---
+ | - metadata:
+ |   - name: min("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [-1]
+ | ...
+SELECT min("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+ | ---
+ | - metadata:
+ |   - name: min("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [-1.5]
+ | ...
+SELECT min("_auto_field_") FROM (values(true), (true), (true));
+ | ---
+ | - metadata:
+ |   - name: min("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - [true]
+ | ...
+SELECT min("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+ | ---
+ | - metadata:
+ |   - name: min("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - ['abc']
+ | ...
+SELECT min("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+ | ---
+ | - metadata:
+ |   - name: min("_auto_field_")
+ |     type: scalar
+ |   rows:
+ |   - ['34']
+ | ...
+
 -- Function sum
 SELECT sum() FROM (values(1), (2), (3));
  | ---
@@ -800,6 +2379,54 @@ SELECT sum("_auto_field_", 2) FROM (values(1), (2), (3));
  | - 'Wrong number of arguments is passed to SUM(): expected 1, got 2'
  | ...
 
+SELECT sum("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+ | ---
+ | - metadata:
+ |   - name: sum("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [null]
+ | ...
+SELECT sum("_auto_field_") FROM (values(1), (1), (1));
+ | ---
+ | - metadata:
+ |   - name: sum("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [3]
+ | ...
+SELECT sum("_auto_field_") FROM (values(-1), (-1), (-1));
+ | ---
+ | - metadata:
+ |   - name: sum("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [-3]
+ | ...
+SELECT sum("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+ | ---
+ | - metadata:
+ |   - name: sum("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [-4.5]
+ | ...
+SELECT sum("_auto_field_") FROM (values(true), (true), (true));
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to number'
+ | ...
+SELECT sum("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert abc to number'
+ | ...
+SELECT sum("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert varbinary to number'
+ | ...
+
 -- Function total
 SELECT total() FROM (values(1), (2), (3));
  | ---
@@ -819,3 +2446,51 @@ SELECT total("_auto_field_", 2) FROM (values(1), (2), (3));
  | - null
  | - 'Wrong number of arguments is passed to TOTAL(): expected 1, got 2'
  | ...
+
+SELECT total("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+ | ---
+ | - metadata:
+ |   - name: total("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [0]
+ | ...
+SELECT total("_auto_field_") FROM (values(1), (1), (1));
+ | ---
+ | - metadata:
+ |   - name: total("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [3]
+ | ...
+SELECT total("_auto_field_") FROM (values(-1), (-1), (-1));
+ | ---
+ | - metadata:
+ |   - name: total("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [-3]
+ | ...
+SELECT total("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+ | ---
+ | - metadata:
+ |   - name: total("_auto_field_")
+ |     type: number
+ |   rows:
+ |   - [-4.5]
+ | ...
+SELECT total("_auto_field_") FROM (values(true), (true), (true));
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert TRUE to number'
+ | ...
+SELECT total("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert abc to number'
+ | ...
+SELECT total("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+ | ---
+ | - null
+ | - 'Type mismatch: can not convert varbinary to number'
+ | ...
diff --git a/test/sql/gh-4159-function-argumens.test.sql b/test/sql/gh-4159-function-argumens.test.sql
index 7578768cb..e45f62d91 100644
--- a/test/sql/gh-4159-function-argumens.test.sql
+++ b/test/sql/gh-4159-function-argumens.test.sql
@@ -3,92 +3,228 @@ SELECT abs();
 SELECT abs(1);
 SELECT abs(1, 2);
 
+SELECT abs(NULL);
+SELECT abs(1);
+SELECT abs(-1);
+SELECT abs(-1.5);
+SELECT abs(true);
+SELECT abs('abc');
+SELECT abs(X'3334');
+
 -- Function char().
 SELECT char();
 SELECT char(1);
 SELECT char(1, 2);
 
+SELECT char(NULL);
+SELECT char(1);
+SELECT char(-1);
+SELECT char(-1.5);
+-- SELECT char(true);
+-- SELECT char('abc');
+-- SELECT char(X'3334');
+
 -- Function character_length().
 SELECT character_length();
 SELECT character_length('1');
 SELECT character_length('1', '2');
 
+SELECT character_length(NULL);
+SELECT character_length(1);
+SELECT character_length(-1);
+SELECT character_length(-1.5);
+SELECT character_length(true);
+SELECT character_length('abc');
+SELECT character_length(X'3334');
+
 -- Function char_length().
 SELECT char_length();
 SELECT char_length('1');
 SELECT char_length('1', '2');
 
+SELECT char_length(NULL);
+SELECT char_length(1);
+SELECT char_length(-1);
+SELECT char_length(-1.5);
+SELECT char_length(true);
+SELECT char_length('abc');
+SELECT char_length(X'3334');
+
 -- Function coalesce().
 SELECT coalesce();
 SELECT coalesce('1');
 SELECT coalesce('1', '2');
 SELECT coalesce('1', '2', '3');
 
+SELECT coalesce(NULL, NULL);
+SELECT coalesce(1, 1);
+SELECT coalesce(-1, -1);
+SELECT coalesce(-1.5, -1.5);
+SELECT coalesce(true, true);
+SELECT coalesce('abc', 'abc');
+SELECT coalesce(X'3334', X'3334');
+
 -- Function greatest().
 SELECT greatest();
 SELECT greatest('1');
 SELECT greatest('1', '2');
 
+SELECT greatest(NULL, NULL);
+SELECT greatest(1, 1);
+SELECT greatest(-1, -1);
+SELECT greatest(-1.5, -1.5);
+SELECT greatest(true, true);
+SELECT greatest('abc', 'abc');
+SELECT greatest(X'3334', X'3334');
+
 -- Function hex().
 SELECT hex();
 SELECT hex(X'33');
 SELECT hex(X'33', X'33');
 
+SELECT hex(NULL);
+SELECT hex(1);
+SELECT hex(-1);
+SELECT hex(-1.5);
+SELECT hex(true);
+SELECT hex('abc');
+SELECT hex(X'3334');
+
 -- Function ifnull
 SELECT ifnull();
 SELECT ifnull(1);
 SELECT ifnull(1, 2);
 SELECT ifnull(1, 2, 3);
 
+SELECT ifnull(NULL, NULL);
+SELECT ifnull(1, 1);
+SELECT ifnull(-1, -1);
+SELECT ifnull(-1.5, -1.5);
+SELECT ifnull(true, true);
+SELECT ifnull('abc', 'abc');
+SELECT ifnull(X'3334', X'3334');
+
 -- Function least().
 SELECT least();
 SELECT least('1');
 SELECT least('1', '2');
 
+SELECT least(NULL, NULL);
+SELECT least(1, 1);
+SELECT least(-1, -1);
+SELECT least(-1.5, -1.5);
+SELECT least(true, true);
+SELECT least('abc', 'abc');
+SELECT least(X'3334', X'3334');
+
 -- Function length().
 SELECT length();
 SELECT length('1');
 SELECT length('1', '2');
 
+SELECT length(NULL);
+SELECT length(1);
+SELECT length(-1);
+SELECT length(-1.5);
+SELECT length(true);
+SELECT length('abc');
+SELECT length(X'3334');
+
 -- Function likelihood
 SELECT likelihood();
 SELECT likelihood(1);
 SELECT likelihood(1, 0.5);
 SELECT likelihood(1, 0.5, 3);
 
+SELECT likelihood(NULL, NULL);
+SELECT likelihood(1, 1);
+SELECT likelihood(-1, -1);
+SELECT likelihood(-1.5, -1.5);
+SELECT likelihood(true, true);
+SELECT likelihood('abc', 'abc');
+SELECT likelihood(X'3334', X'3334');
+
 -- Function likely
 SELECT likely();
 SELECT likely(1);
 SELECT likely(1, 2);
 
+SELECT likely(NULL);
+SELECT likely(1);
+SELECT likely(-1);
+SELECT likely(-1.5);
+SELECT likely(true);
+SELECT likely('abc');
+SELECT likely(X'3334');
+
 -- Function lower
 SELECT lower();
 SELECT lower('a');
 SELECT lower('a', 2);
 
+SELECT lower(NULL);
+SELECT lower(1);
+SELECT lower(-1);
+SELECT lower(-1.5);
+SELECT lower(true);
+SELECT lower('abc');
+SELECT lower(X'3334');
+
 -- Function nullif
 SELECT nullif();
 SELECT nullif(1);
 SELECT nullif(1, 2);
 SELECT nullif(1, 2, 3);
 
+SELECT nullif(NULL, NULL);
+SELECT nullif(1, 1);
+SELECT nullif(-1, -1);
+SELECT nullif(-1.5, -1.5);
+SELECT nullif(true, true);
+SELECT nullif('abc', 'abc');
+SELECT nullif(X'3334', X'3334');
+
 -- Function position
 SELECT position();
 SELECT position('12345');
 SELECT position('12345', '2');
 SELECT position('12345', '2', 3);
 
+SELECT position(NULL, NULL);
+SELECT position(1, 1);
+SELECT position(-1, -1);
+SELECT position(-1.5, -1.5);
+SELECT position(true, true);
+SELECT position('abc', 'abc');
+SELECT position(X'3334', X'3334');
+
 -- Function printf
 SELECT printf();
 SELECT printf('1');
 SELECT printf('1', 2);
 SELECT printf('1', 2, 3);
 
+SELECT printf(NULL, NULL);
+SELECT printf(1, 1);
+SELECT printf(-1, -1);
+SELECT printf(-1.5, -1.5);
+SELECT printf(true, true);
+SELECT printf('abc', 'abc');
+SELECT printf(X'3334', X'3334');
+
 -- Function quote
 SELECT quote();
 SELECT quote('1');
 SELECT quote('1', 2);
 
+SELECT quote(NULL);
+SELECT quote(1);
+SELECT quote(-1);
+SELECT quote(-1.5);
+SELECT quote(true);
+SELECT quote('abc');
+SELECT quote(X'3334');
+
 -- Function random
 SELECT typeof(random());
 SELECT typeof(random(1));
@@ -98,6 +234,14 @@ SELECT typeof(randomblob());
 SELECT typeof(randomblob(1));
 SELECT typeof(randomblob(1, 2));
 
+SELECT randomblob(NULL);
+SELECT typeof(randomblob(1));
+SELECT typeof(randomblob(-1));
+SELECT typeof(randomblob(-1.5));
+SELECT typeof(randomblob(true));
+SELECT typeof(randomblob('abc'));
+SELECT typeof(randomblob(X'3334'));
+
 -- Function replace
 SELECT replace();
 SELECT replace('12345');
@@ -105,12 +249,28 @@ SELECT replace('12345', '2');
 SELECT replace('12345', '2', '3');
 SELECT replace('12345', '2', '3', 4);
 
+SELECT replace(NULL, NULL, NULL);
+SELECT replace(1, 1, 1);
+SELECT replace(-1, -1, -1);
+SELECT replace(-1.5, -1.5, -1.5);
+SELECT replace(true, true, true);
+SELECT replace('abc', 'abc', 'abc');
+SELECT replace(X'3334', X'3334', X'3334');
+
 -- Function round
 SELECT round();
 SELECT round(1.1245);
 SELECT round(1.1245, 2);
 SELECT round(1.1245, 2, 3);
 
+SELECT round(NULL, NULL);
+SELECT round(1, 1);
+SELECT round(-1, -1);
+SELECT round(-1.5, -1.5);
+SELECT round(true, true);
+SELECT round('abc', 'abc');
+SELECT round(X'3334', X'3334');
+
 -- Function row_count
 SELECT row_count();
 SELECT row_count(1);
@@ -120,6 +280,14 @@ SELECT soundex();
 SELECT soundex(1);
 SELECT soundex(1, 2);
 
+SELECT soundex(NULL);
+SELECT soundex(1);
+SELECT soundex(-1);
+SELECT soundex(-1.5);
+SELECT soundex(true);
+SELECT soundex('abc');
+SELECT soundex(X'3334');
+
 -- Function substr
 SELECT substr();
 SELECT substr('12345');
@@ -127,26 +295,66 @@ SELECT substr('12345', 2);
 SELECT substr('12345', 2, 3);
 SELECT substr('12345', 2, 3, 4);
 
+SELECT substr(NULL, NULL);
+SELECT substr(1, 1);
+SELECT substr(-1, -1);
+SELECT substr(-1.5, -1.5);
+SELECT substr(true, true);
+SELECT substr('abc', 'abc');
+SELECT substr(X'3334', X'3334');
+
 -- Function typeof
 SELECT typeof();
 SELECT typeof(1);
 SELECT typeof(1, 2);
 
+SELECT typeof(NULL);
+SELECT typeof(1);
+SELECT typeof(-1);
+SELECT typeof(-1.5);
+SELECT typeof(true);
+SELECT typeof('abc');
+SELECT typeof(X'3334');
+
 -- Function unicode
 SELECT unicode();
 SELECT unicode('1');
 SELECT unicode('1', '2');
 
+SELECT unicode(NULL);
+SELECT unicode(1);
+SELECT unicode(-1);
+SELECT unicode(-1.5);
+SELECT unicode(true);
+SELECT unicode('abc');
+SELECT unicode(X'3334');
+
 -- Function unlikely
 SELECT unlikely();
 SELECT unlikely(1);
 SELECT unlikely(1, 2);
 
+SELECT unlikely(NULL);
+SELECT unlikely(1);
+SELECT unlikely(-1);
+SELECT unlikely(-1.5);
+SELECT unlikely(true);
+SELECT unlikely('abc');
+SELECT unlikely(X'3334');
+
 -- Function upper
 SELECT upper();
 SELECT upper('a');
 SELECT upper('a', 2);
 
+SELECT upper(NULL);
+SELECT upper(1);
+SELECT upper(-1);
+SELECT upper(-1.5);
+SELECT upper(true);
+SELECT upper('abc');
+SELECT upper(X'3334');
+
 -- Function version
 SELECT typeof(version());
 SELECT typeof(version(1));
@@ -156,38 +364,102 @@ SELECT zeroblob();
 SELECT zeroblob(1);
 SELECT zeroblob(1, 2);
 
+SELECT zeroblob(NULL);
+SELECT zeroblob(1);
+SELECT zeroblob(-1);
+SELECT zeroblob(-1.5);
+SELECT zeroblob(true);
+SELECT zeroblob('abc');
+SELECT zeroblob(X'3334');
+
 -- Function avg
 SELECT avg() FROM (values(1), (2), (3));
 SELECT avg("_auto_field_") FROM (values(1), (2), (3));
 SELECT avg("_auto_field_", 2) FROM (values(1), (2), (3));
 
+SELECT avg("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+SELECT avg("_auto_field_") FROM (values(1), (1), (1));
+SELECT avg("_auto_field_") FROM (values(-1), (-1), (-1));
+SELECT avg("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+SELECT avg("_auto_field_") FROM (values(true), (true), (true));
+SELECT avg("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+SELECT avg("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+
 -- Function count
 SELECT count() FROM (values(1), (2), (3));
 SELECT count("_auto_field_") FROM (values(1), (2), (3));
 SELECT count("_auto_field_", 2) FROM (values(1), (2), (3));
 
+SELECT count("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+SELECT count("_auto_field_") FROM (values(1), (1), (1));
+SELECT count("_auto_field_") FROM (values(-1), (-1), (-1));
+SELECT count("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+SELECT count("_auto_field_") FROM (values(true), (true), (true));
+SELECT count("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+SELECT count("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+
 -- Function group_concat
 SELECT group_concat() FROM (values('1'), ('2'), ('3'));
 SELECT group_concat("_auto_field_") FROM (values('1'), ('2'), ('3'));
 SELECT group_concat("_auto_field_", '2') FROM (values('1'), ('2'), ('3'));
 SELECT group_concat("_auto_field_", '2', '3') FROM (values('1'), ('2'), ('3'));
 
+SELECT group_concat("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+SELECT group_concat("_auto_field_") FROM (values(1), (1), (1));
+SELECT group_concat("_auto_field_") FROM (values(-1), (-1), (-1));
+SELECT group_concat("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+SELECT group_concat("_auto_field_") FROM (values(true), (true), (true));
+SELECT group_concat("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+SELECT group_concat("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+
 -- Function max
 SELECT max() FROM (values(1), (2), (3));
 SELECT max("_auto_field_") FROM (values(1), (2), (3));
 SELECT max("_auto_field_", 2) FROM (values(1), (2), (3));
 
+SELECT max("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+SELECT max("_auto_field_") FROM (values(1), (1), (1));
+SELECT max("_auto_field_") FROM (values(-1), (-1), (-1));
+SELECT max("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+SELECT max("_auto_field_") FROM (values(true), (true), (true));
+SELECT max("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+SELECT max("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+
 -- Function min
 SELECT min() FROM (values(1), (2), (3));
 SELECT min("_auto_field_") FROM (values(1), (2), (3));
 SELECT min("_auto_field_", 2) FROM (values(1), (2), (3));
 
+SELECT min("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+SELECT min("_auto_field_") FROM (values(1), (1), (1));
+SELECT min("_auto_field_") FROM (values(-1), (-1), (-1));
+SELECT min("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+SELECT min("_auto_field_") FROM (values(true), (true), (true));
+SELECT min("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+SELECT min("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+
 -- Function sum
 SELECT sum() FROM (values(1), (2), (3));
 SELECT sum("_auto_field_") FROM (values(1), (2), (3));
 SELECT sum("_auto_field_", 2) FROM (values(1), (2), (3));
 
+SELECT sum("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+SELECT sum("_auto_field_") FROM (values(1), (1), (1));
+SELECT sum("_auto_field_") FROM (values(-1), (-1), (-1));
+SELECT sum("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+SELECT sum("_auto_field_") FROM (values(true), (true), (true));
+SELECT sum("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+SELECT sum("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
+
 -- Function total
 SELECT total() FROM (values(1), (2), (3));
 SELECT total("_auto_field_") FROM (values(1), (2), (3));
 SELECT total("_auto_field_", 2) FROM (values(1), (2), (3));
+
+SELECT total("_auto_field_") FROM (values(NULL), (NULL), (NULL));
+SELECT total("_auto_field_") FROM (values(1), (1), (1));
+SELECT total("_auto_field_") FROM (values(-1), (-1), (-1));
+SELECT total("_auto_field_") FROM (values(-1.5), (-1.5), (-1.5));
+SELECT total("_auto_field_") FROM (values(true), (true), (true));
+SELECT total("_auto_field_") FROM (values('abc'), ('abc'), ('abc'));
+SELECT total("_auto_field_") FROM (values(X'3334'), (X'3334'), (X'3334'));
diff --git a/test/sql/types.result b/test/sql/types.result
index 70fbbc5a2..0fdfa1283 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -208,32 +208,29 @@ box.execute("CREATE TABLE t1 (s SCALAR PRIMARY KEY);")
 ---
 - row_count: 1
 ...
-box.execute("INSERT INTO t1 VALUES (randomblob(5));")
+box.execute("INSERT INTO t1 VALUES (X'FF3435');")
 ---
 - row_count: 1
 ...
 box.execute("SELECT * FROM t1 WHERE s LIKE 'blob';")
 ---
 - null
-- 'Inconsistent types: expected text got varbinary'
+- 'Type mismatch: can not convert varbinary to string'
 ...
 box.execute("SELECT * FROM t1 WHERE 'blob' LIKE s;")
 ---
 - null
-- 'Inconsistent types: expected text got varbinary'
+- 'Type mismatch: can not convert varbinary to string'
 ...
 box.execute("SELECT * FROM t1 WHERE 'blob' LIKE x'0000';")
 ---
 - null
-- 'Inconsistent types: expected text got varbinary'
+- 'Type mismatch: can not convert varbinary to string'
 ...
 box.execute("SELECT s LIKE NULL FROM t1;")
 ---
-- metadata:
-  - name: s LIKE NULL
-    type: integer
-  rows:
-  - [null]
+- null
+- 'Type mismatch: can not convert varbinary to string'
 ...
 box.execute("DELETE FROM t1;")
 ---
@@ -246,20 +243,17 @@ box.execute("INSERT INTO t1 VALUES (1);")
 box.execute("SELECT * FROM t1 WHERE s LIKE 'int';")
 ---
 - null
-- 'Inconsistent types: expected text got unsigned'
+- 'Type mismatch: can not convert 1 to string'
 ...
 box.execute("SELECT * FROM t1 WHERE 'int' LIKE 4;")
 ---
 - null
-- 'Inconsistent types: expected text got unsigned'
+- 'Type mismatch: can not convert 4 to string'
 ...
 box.execute("SELECT NULL LIKE s FROM t1;")
 ---
-- metadata:
-  - name: NULL LIKE s
-    type: integer
-  rows:
-  - [null]
+- null
+- 'Type mismatch: can not convert 1 to string'
 ...
 box.space.T1:drop()
 ---
@@ -830,19 +824,13 @@ box.execute("DELETE FROM t WHERE i < 18446744073709551613;")
 ...
 box.execute("SELECT lower(i) FROM t;")
 ---
-- metadata:
-  - name: lower(i)
-    type: string
-  rows:
-  - ['18446744073709551613']
+- null
+- 'Type mismatch: can not convert 18446744073709551613 to string'
 ...
 box.execute("SELECT upper(i) FROM t;")
 ---
-- metadata:
-  - name: upper(i)
-    type: string
-  rows:
-  - ['18446744073709551613']
+- null
+- 'Type mismatch: can not convert 18446744073709551613 to string'
 ...
 box.execute("SELECT abs(i) FROM t;")
 ---
@@ -1315,18 +1303,24 @@ box.execute("SELECT group_concat(v) FROM t;")
 ...
 box.execute("SELECT lower(v) FROM t;")
 ---
-- null
-- 'Inconsistent types: expected text got varbinary'
+- metadata:
+  - name: lower(v)
+    type: string
+  rows:
+  - ['abc']
 ...
 box.execute("SELECT upper(v) FROM t;")
 ---
-- null
-- 'Inconsistent types: expected text got varbinary'
+- metadata:
+  - name: upper(v)
+    type: string
+  rows:
+  - ['ABC']
 ...
 box.execute("SELECT abs(v) FROM t;")
 ---
 - null
-- 'Inconsistent types: expected number got varbinary'
+- 'Type mismatch: can not convert varbinary to number'
 ...
 box.execute("SELECT typeof(v) FROM t;")
 ---
@@ -1883,25 +1877,13 @@ box.execute("SELECT group_concat(d) FROM t;")
 ...
 box.execute("SELECT lower(d) FROM t;")
 ---
-- metadata:
-  - name: lower(d)
-    type: string
-  rows:
-  - ['10.0']
-  - ['-2.0']
-  - ['3.3']
-  - ['1.8e+19']
+- null
+- 'Type mismatch: can not convert 10.0 to string'
 ...
 box.execute("SELECT upper(d) FROM t;")
 ---
-- metadata:
-  - name: upper(d)
-    type: string
-  rows:
-  - ['10.0']
-  - ['-2.0']
-  - ['3.3']
-  - ['1.8E+19']
+- null
+- 'Type mismatch: can not convert 10.0 to string'
 ...
 box.execute("SELECT abs(d) FROM t;")
 ---
diff --git a/test/sql/types.test.lua b/test/sql/types.test.lua
index 2dc70f3c5..07158708e 100644
--- a/test/sql/types.test.lua
+++ b/test/sql/types.test.lua
@@ -59,7 +59,7 @@ box.execute("VALUES (TYPEOF(randomblob(5) || zeroblob(5)));")
 -- gh-3954: LIKE accepts only arguments of type TEXT and NULLs.
 --
 box.execute("CREATE TABLE t1 (s SCALAR PRIMARY KEY);")
-box.execute("INSERT INTO t1 VALUES (randomblob(5));")
+box.execute("INSERT INTO t1 VALUES (X'FF3435');")
 box.execute("SELECT * FROM t1 WHERE s LIKE 'blob';")
 box.execute("SELECT * FROM t1 WHERE 'blob' LIKE s;")
 box.execute("SELECT * FROM t1 WHERE 'blob' LIKE x'0000';")
-- 
2.25.1

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH v4 1/5] sql: set field_type in mem_set_*() functions
  2020-07-13  5:32 ` [Tarantool-patches] [PATCH v4 1/5] sql: set field_type in mem_set_*() functions imeevma
@ 2020-07-13 10:36   ` Nikita Pettik
  0 siblings, 0 replies; 11+ messages in thread
From: Nikita Pettik @ 2020-07-13 10:36 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

On 13 Jul 08:32, imeevma@tarantool.org wrote:
> After this patch, the mem_set _*() functions will set the mem field type
> along with its MEM type flag.

Nice. All tests seem passing. So what was the problem in this
refactoring? I 'member you said that it couldn't be done with ease.
LGTM but I would put justification in commit message.

> ---
>  src/box/sql/vdbemem.c | 10 ++++++++++
>  1 file changed, 10 insertions(+)
> 
> diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
> index 2e0d0bc3b..8e9ebf7ab 100644
> --- a/src/box/sql/vdbemem.c
> +++ b/src/box/sql/vdbemem.c
> @@ -829,6 +829,7 @@ mem_set_bool(struct Mem *mem, bool value)
>  	sqlVdbeMemSetNull(mem);
>  	mem->u.b = value;
>  	mem->flags = MEM_Bool;
> +	mem->field_type = FIELD_TYPE_BOOLEAN;
>  }
>  
>  void
> @@ -839,6 +840,7 @@ mem_set_i64(struct Mem *mem, int64_t value)
>  	mem->u.i = value;
>  	int flag = value < 0 ? MEM_Int : MEM_UInt;
>  	MemSetTypeFlag(mem, flag);
> +	mem->field_type = FIELD_TYPE_INTEGER;
>  }
>  
>  void
> @@ -848,6 +850,7 @@ mem_set_u64(struct Mem *mem, uint64_t value)
>  		sqlVdbeMemSetNull(mem);
>  	mem->u.u = value;
>  	MemSetTypeFlag(mem, MEM_UInt);
> +	mem->field_type = FIELD_TYPE_UNSIGNED;
>  }
>  
>  void
> @@ -863,6 +866,7 @@ mem_set_int(struct Mem *mem, int64_t value, bool is_neg)
>  		mem->u.u = value;
>  		MemSetTypeFlag(mem, MEM_UInt);
>  	}
> +	mem->field_type = FIELD_TYPE_INTEGER;
>  }
>  
>  void
> @@ -873,6 +877,7 @@ mem_set_double(struct Mem *mem, double value)
>  		return;
>  	mem->u.r = value;
>  	MemSetTypeFlag(mem, MEM_Real);
> +	mem->field_type = FIELD_TYPE_DOUBLE;
>  }
>  
>  /*
> @@ -1068,6 +1073,11 @@ sqlVdbeMemSetStr(Mem * pMem,	/* Memory cell to set to string value */
>  
>  	pMem->n = nByte;
>  	pMem->flags = flags;
> +	assert((pMem->flags & (MEM_Str | MEM_Blob)) != 0);
> +	if ((pMem->flags & MEM_Str) != 0)
> +		pMem->field_type = FIELD_TYPE_STRING;
> +	else
> +		pMem->field_type = FIELD_TYPE_VARBINARY;
>  
>  	if (nByte > iLimit) {
>  		diag_set(ClientError, ER_SQL_EXECUTE, "string or binary string"\
> -- 
> 2.25.1
> 

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH v4 2/5] sql: move diag setting to sql_func_by_signature()
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 2/5] sql: move diag setting to sql_func_by_signature() imeevma
@ 2020-07-13 10:58   ` Nikita Pettik
  0 siblings, 0 replies; 11+ messages in thread
From: Nikita Pettik @ 2020-07-13 10:58 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

On 13 Jul 08:33, imeevma@tarantool.org wrote:
> After this patch, the sql_func_by_signature() function will check the
> found function and set diag if something is wrong.

Why we are doing this refactoring? I've bee begging you to provide
more descriptive commit messages for past two years.
 
> Needed for #4159
> ---
>  src/box/sql/expr.c    |  2 --
>  src/box/sql/func.c    | 18 +++++++++++++++---
>  src/box/sql/resolve.c | 23 ++---------------------
>  src/box/sql/sqlInt.h  |  7 +++++--
>  4 files changed, 22 insertions(+), 28 deletions(-)
> 
> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index bc2182446..d0620b98c 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -3978,8 +3978,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
>  			zId = pExpr->u.zToken;
>  			struct func *func = sql_func_by_signature(zId, nFarg);
>  			if (func == NULL) {
> -				diag_set(ClientError, ER_NO_SUCH_FUNCTION,
> -					 zId);
>  				pParse->is_aborted = true;
>  				break;
>  			}
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 487cdafe1..4bbe4d4b7 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -2185,11 +2185,23 @@ struct func *
>  sql_func_by_signature(const char *name, int argc)
>  {
>  	struct func *base = func_by_name(name, strlen(name));
> -	if (base == NULL || !base->def->exports.sql)
> +	if (base == NULL) {
> +		diag_set(ClientError, ER_NO_SUCH_FUNCTION, name);
>  		return NULL;
> -
> -	if (base->def->param_count != -1 && base->def->param_count != argc)
> +	}
> +	if (!base->def->exports.sql) {
> +		diag_set(ClientError, ER_SQL_PARSER_GENERIC,
> +			 tt_sprintf("function %s() is not available in SQL",
> +				     name));
> +		return NULL;
> +	}
> +	int param_count = base->def->param_count;
> +	if (param_count != -1 && param_count != argc) {
> +		const char *err = tt_sprintf("%d", param_count);
> +		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
> +			 base->def->name, err, argc);
>  		return NULL;
> +	}
>  	return base;
>  }
>  
> diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
> index 6f625dc18..10c77c491 100644
> --- a/src/box/sql/resolve.c
> +++ b/src/box/sql/resolve.c
> @@ -598,32 +598,13 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
>  			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
>  			zId = pExpr->u.zToken;
>  			nId = sqlStrlen30(zId);
> -			struct func *func = func_by_name(zId, nId);
> +			struct func *func = sql_func_by_signature(zId, n);
>  			if (func == NULL) {
> -				diag_set(ClientError, ER_NO_SUCH_FUNCTION, zId);
> -				pParse->is_aborted = true;
> -				pNC->nErr++;
> -				return WRC_Abort;
> -			}
> -			if (!func->def->exports.sql) {
> -				diag_set(ClientError, ER_SQL_PARSER_GENERIC,
> -					 tt_sprintf("function %.*s() is not "
> -						    "available in SQL",
> -						     nId, zId));
> -				pParse->is_aborted = true;
> -				pNC->nErr++;
> -				return WRC_Abort;
> -			}
> -			if (func->def->param_count != -1 &&
> -			    func->def->param_count != n) {
> -				uint32_t argc = func->def->param_count;
> -				const char *err = tt_sprintf("%d", argc);
> -				diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
> -					 func->def->name, err, n);
>  				pParse->is_aborted = true;
>  				pNC->nErr++;
>  				return WRC_Abort;
>  			}
> +			assert(func->def->exports.sql);
>  			bool is_agg = func->def->aggregate ==
>  				      FUNC_AGGREGATE_GROUP;
>  			assert(!is_agg || func->def->language ==
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 37283e506..58a65acc1 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -4441,8 +4441,11 @@ sql_func_flag_is_set(struct func *func, uint16_t flag)
>   * export field set true and have exactly the same signature
>   * are returned.
>   *
> - * Returns not NULL function pointer when a valid and exported
> - * to SQL engine function is found and NULL otherwise.
> + * @param name Name of the function to find.
> + * @param argc Number of arguments of the function.
> + *
> + * @retval not NULL function pointer when a function is found.
> + * @retval NULL on error and sets a diag.
>   */
>  struct func *
>  sql_func_by_signature(const char *name, int argc);
> -- 
> 2.25.1
> 

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH v4 3/5] sql: check number of arguments in sql_func_by_signature()
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 3/5] sql: check number of arguments in sql_func_by_signature() imeevma
@ 2020-07-13 12:21   ` Nikita Pettik
  0 siblings, 0 replies; 11+ messages in thread
From: Nikita Pettik @ 2020-07-13 12:21 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

On 13 Jul 08:33, imeevma@tarantool.org wrote:
> After this patch, the number of function arguments will always be
> checked in the sql_func_by_signature() function. This was not the
> case for some of the built-in functions.

At least, I'd split patch into two parts: first introduces new
members and changes data structures, and the second one - adds
new check (if it is necessary at all; see comments below).
 
> Part of #4159
> ---
> @@ -2195,11 +2164,28 @@ sql_func_by_signature(const char *name, int argc)
>  				     name));
>  		return NULL;
>  	}

It looks strange since other 'by' getters (space, index, collation)
dont set diag errors.

> -	int param_count = base->def->param_count;
> -	if (param_count != -1 && param_count != argc) {
> -		const char *err = tt_sprintf("%d", param_count);
> -		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
> -			 base->def->name, err, argc);
> +	if (base->def->language != FUNC_LANGUAGE_SQL_BUILTIN) {
> +		int param_count = base->def->param_count;
> +		if (param_count != -1 && param_count != argc) {
> +			const char *err = tt_sprintf("%d", param_count);
> +			diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
> +				 base->def->name, err, argc);
> +			return NULL;
> +		}
> +		return base;
> +	}
> +	struct func_sql_builtin *func = (struct func_sql_builtin *)base;
> +	uint32_t arg_c = (uint32_t)argc;
> +	if (func->args.min_count > arg_c || func->args.max_count < arg_c) {
> +		const char *err;
> +		uint32_t min = func->args.min_count;
> +		uint32_t max = func->args.max_count;
> +		if (min != max)
> +			err = tt_sprintf("from %d to %d", min, max);
> +		else
> +			err = tt_sprintf("%d", min);
> +		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, base->def->name,
> +			 err, argc);
>  		return NULL;
>  	}
>  	return base;
> @@ -2242,12 +2228,16 @@ static struct {
>  	/** Members below are related to struct func_def. */
>  	bool is_deterministic;
>  	int param_count;
> +	uint32_t min_count;
> +	uint32_t max_count;

Why not erase param_count?
min/max_count of what? Also add comments to these members explaining
why do we need them at all.
Why can't we use -1 param_count for variadic args and provide check
in func's implementation?

>  	enum field_type returns;
>  	enum func_aggregate aggregate;
>  	bool export_to_sql;
>  } sql_builtins[] = {
>  	{.name = "ABS",
>  	 .param_count = 1,
> +	 .min_count = 1,
> +	 .max_count = 1,
>  	 .returns = FIELD_TYPE_NUMBER,
>  	 .aggregate = FUNC_AGGREGATE_NONE,
>  	 .is_deterministic = true,
> @@ -2318,6 +2320,8 @@ static struct {
>  	}, {
>  	 .name = "COALESCE",
>  	 .param_count = -1,
> +	 .min_count = 2,
> +	 .max_count = SQL_MAX_FUNCTION_ARG,

Why do you need uint32 if max value fits into uint8?

> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 58a65acc1..6af9d7473 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -4397,11 +4397,24 @@ Expr *sqlExprForVectorField(Parse *, Expr *, int);
>   */
>  extern int sqlSubProgramsRemaining;
>  
> +/**
> + * A structure that contains additional information about
> + * arguments to built-in SQL functions.
> + */
> +struct sql_builtin_func_args {
> +	/** Min number of arguments. */
> +	uint32_t min_count;
> +	/** Max number of arguments. */
> +	uint32_t max_count;
> +};
> +
>  struct func_sql_builtin {
>  	/** Function object base class. */
>  	struct func base;
>  	/** A bitmask of SQL flags. */
>  	uint16_t flags;
> +	/** Information about arguments to built-in functions. */
> +	struct sql_builtin_func_args args;

Why not simply inline two members (min/max)?

>  	/**
>  	 * A VDBE-memory-compatible call method.
>  	 * SQL built-ins don't use func base class "call"
> diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
> index 3c088920f..1d3ef9e2a 100755
> --- a/test/sql-tap/func.test.lua
> +++ b/test/sql-tap/func.test.lua
> @@ -428,7 +428,7 @@ test:do_catchsql_test(
>          SELECT round(a,b,c) FROM t1
>      ]], {
>          -- <func-4.5>
> -        1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 3"
> +        1, "Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 3"
>          -- </func-4.5>
>      })
>  
> @@ -488,7 +488,7 @@ test:do_catchsql_test(
>          SELECT round() FROM t1 ORDER BY a
>      ]], {
>          -- <func-4.11>
> -        1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 0"
> +        1, "Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 0"
>          -- </func-4.11>
>      })
>  
> @@ -2540,7 +2540,7 @@ test:do_catchsql_test(
>          SELECT coalesce()
>      ]], {
>          -- <func-27.1>
> -        1, "Wrong number of arguments is passed to COALESCE(): expected at least two, got 0"
> +        1, "Wrong number of arguments is passed to COALESCE(): expected from 2 to 127, got 0"

Current error message looks better than new one (127 arguments??).

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH v4 4/5] sql: change implicit cast for assignment
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 4/5] sql: change implicit cast for assignment imeevma
@ 2020-07-13 14:42   ` Nikita Pettik
  0 siblings, 0 replies; 11+ messages in thread
From: Nikita Pettik @ 2020-07-13 14:42 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

On 13 Jul 08:33, imeevma@tarantool.org wrote:
> +
>   * Synopsis: type(r[P1@P2])
>   *
> - * Apply types to a range of P2 registers starting with P1.
> - *
> - * P4 is a string that is P2 characters long. The nth character of the
> - * string indicates the column type that should be used for the nth
> - * memory cell in the range.
> + * Check that types of P2 registers starting from register P1 are
> + * compatible with given field types in P4. If the MEM_type of the
> + * value and the given type are incompatible according to
> + * field_mp_plain_type_is_compatible(), but both are numeric,
> + * this opcode attempts to convert the value to the type.
>   */
>  case OP_ApplyType: {
>  	enum field_type *types = pOp->p4.types;
> @@ -2762,7 +2900,13 @@ case OP_ApplyType: {
>  	while((type = *(types++)) != field_type_MAX) {
>  		assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
>  		assert(memIsValid(pIn1));
> -		if (mem_apply_type(pIn1, type) != 0) {
> +		if (mem_is_type_compatible(pIn1, type)) {
> +			pIn1++;
> +			continue;
> +		}
> +		if (!mp_type_is_numeric(mem_mp_type(pIn1)) ||
> +		    !sql_type_is_numeric(type) ||
> +		    mem_convert_to_numeric(pIn1, type, false) != 0) {

Consider refactoring:

diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 863f38f5d..41a4750da 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2900,19 +2900,23 @@ case OP_ApplyType: {
        while((type = *(types++)) != field_type_MAX) {
                assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
                assert(memIsValid(pIn1));
-               if (mem_is_type_compatible(pIn1, type)) {
-                       pIn1++;
-                       continue;
-               }
-               if (!mp_type_is_numeric(mem_mp_type(pIn1)) ||
-                   !sql_type_is_numeric(type) ||
-                   mem_convert_to_numeric(pIn1, type, false) != 0) {
-                       diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-                                sql_value_to_diag_str(pIn1),
-                                field_type_strs[type]);
-                       goto abort_due_to_error;
+               if (!mem_is_type_compatible(pIn1, type)) {
+                       /* Implicit cast is allowed only to numeric type. */
+                       if (!sql_type_is_numeric(type))
+                               goto type_mismatch;
+                       /* Implicit cast is allowed only from numeric type. */
+                       if (!mp_type_is_numeric(mem_mp_type(pIn1)))
+                               goto type_mismatch;
+                       /* Try to convert numeric-to-numeric. */
+                       if (mem_convert_to_numeric(pIn1, type, false) != 0)
+                               goto type_mismatch;
                }
                pIn1++;
+               continue;
+type_mismatch:
+               diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+                        sql_value_to_diag_str(pIn1), field_type_strs[type]);
+               goto abort_due_to_error;
        }
        break;
 }

Otherwise LGTM

>  			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
>  				 sql_value_to_diag_str(pIn1),
>  				 field_type_strs[type]);
> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> index 44c27bdb7..ad46ab129 100644
> --- a/src/box/sql/vdbeInt.h
> +++ b/src/box/sql/vdbeInt.h
> @@ -566,6 +566,10 @@ mem_mp_type(struct Mem *mem);
>   */
>  #define mp_type_is_bloblike(X) ((X) == MP_BIN || (X) == MP_ARRAY || (X) == MP_MAP)
>  
> +/** Return TRUE if MP_type of X is numeric, FALSE otherwise. */
> +#define mp_type_is_numeric(X) ((X) == MP_INT || (X) == MP_UINT ||\
> +			       (X) == MP_DOUBLE)

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Tarantool-patches] [PATCH v4 5/5] sql: properly check arguments of functions
  2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 5/5] sql: properly check arguments of functions imeevma
@ 2020-07-13 14:56   ` Nikita Pettik
  0 siblings, 0 replies; 11+ messages in thread
From: Nikita Pettik @ 2020-07-13 14:56 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

On 13 Jul 08:33, imeevma@tarantool.org wrote:
> After this patch, the function arguments will be checked using ApplyType
> opcode before they are passed to the function. This means that the rules
> for implicitly casting values ​​that were specified as arguments are
> defined by this opcode.
> 
> Closes #4159
> ---
>  src/box/sql/expr.c                          |    5 +
>  src/box/sql/func.c                          |  360 +++-
>  src/box/sql/select.c                        |   31 +
>  src/box/sql/sqlInt.h                        |   32 +
>  src/box/sql/vdbe.c                          |   12 +-
>  test/sql-tap/cse.test.lua                   |    8 +-
>  test/sql-tap/func.test.lua                  |   48 +-
>  test/sql-tap/orderby1.test.lua              |    2 +-
>  test/sql-tap/position.test.lua              |    6 +-
>  test/sql/boolean.result                     |   32 +-
>  test/sql/gh-4159-function-argumens.result   | 2131 +++++++++++++++++--
>  test/sql/gh-4159-function-argumens.test.sql |  272 +++
>  test/sql/types.result                       |   76 +-
>  test/sql/types.test.lua                     |    2 +-
>  14 files changed, 2584 insertions(+), 433 deletions(-)
> 
> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index 7aee240a3..aa5477c6a 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -4104,6 +4104,11 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
>  			} else {
>  				r1 = 0;
>  			}
> +			if (func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
> +				struct func_sql_builtin *f =
> +					(struct func_sql_builtin *)func;
> +				sql_emit_func_types(v, &f->args, r1, nFarg);
> +			}
>  			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
>  				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
>  						  (char *)coll, P4_COLLSEQ);
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 9d4c26081..66edc3792 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -459,17 +459,10 @@ static void
>  lengthFunc(sql_context * context, int argc, sql_value ** argv)
>  {
>  	int len;
> -
>  	assert(argc == 1);
>  	UNUSED_PARAMETER(argc);
>  	switch (sql_value_type(argv[0])) {
> -	case MP_BIN:
> -	case MP_ARRAY:
> -	case MP_MAP:
> -	case MP_INT:
> -	case MP_UINT:
> -	case MP_BOOL:
> -	case MP_DOUBLE:{
> +	case MP_BIN: {

Let's get rid of switch-cases, they are really redundant now.
If you are afraid that diff will become big enough, you can
refactor each function in a separate patch.

> @@ -1550,6 +1514,15 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
>  
>  	assert(argc == 3);
>  	UNUSED_PARAMETER(argc);
> +	assert(sql_value_type(argv[0]) == MP_NIL ||
> +	       sql_value_type(argv[0]) == MP_STR ||
> +	       sql_value_type(argv[0]) == MP_BIN);

Let's evaluate each type of argv only once.

> +	assert(sql_value_type(argv[1]) == MP_NIL ||
> +	       sql_value_type(argv[1]) == MP_STR ||
> +	       sql_value_type(argv[1]) == MP_BIN);
> +	assert(sql_value_type(argv[2]) == MP_NIL ||
> +	       sql_value_type(argv[2]) == MP_STR ||
> +	       sql_value_type(argv[2]) == MP_BIN);
>  	zStr = sql_value_text(argv[0]);
>  	if (zStr == 0)
>  		return;
> @@ -1858,13 +1831,9 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
>  		1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
>  	};
>  	assert(argc == 1);
> -	enum mp_type mp_type = sql_value_type(argv[0]);
> -	if (mp_type_is_bloblike(mp_type)) {
> -		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
> -			 sql_value_to_diag_str(argv[0]), "text");
> -		context->is_aborted = true;
> -		return;
> -	}
> +	assert(sql_value_type(argv[0]) == MP_NIL ||
> +	       sql_value_type(argv[0]) == MP_STR ||
> +	       sql_value_type(argv[0]) == MP_BIN);

Ditto

>  	zIn = (u8 *) sql_value_text(argv[0]);
>  	if (zIn == 0)
>  		zIn = (u8 *) "";
>  	int param_count;
>  	uint32_t min_count;
>  	uint32_t max_count;
> +	enum field_type *types;
> +	enum field_type recurrent_type;
> +	bool is_blob_like_str;

What are these members supposed to mean?

>  	enum field_type returns;
>  	enum func_aggregate aggregate;
>  	bool export_to_sql;
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index 4b069addb..fe56ede1b 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -124,6 +124,34 @@ clearSelect(sql * db, Select * p, int bFree)
>  	}
>  }
>  
> +void
> +sql_emit_func_types(struct Vdbe *vdbe, struct sql_builtin_func_args *args,
> +		    int reg, uint32_t argc)

-> sql_emit_func_arg_type_check()

> +{
> +	assert(argc <= args->max_count);
> +	enum field_type recurrent_type = args->recurrent_type;
> +	assert(args->max_count > 0 || recurrent_type == FIELD_TYPE_ANY);
> +	/*
> +	 * It makes no sense to check types of the MEMs if all
> +	 * arguments should be of type ANY.
> +	 */
> +	if (recurrent_type == FIELD_TYPE_ANY)
> +		return;
> +	size_t size = (argc + 1) * sizeof(enum field_type);
> +	enum field_type *types = sqlDbMallocZero(sql_get(), size);
> +	for (uint32_t i = 0; i < argc; ++i) {
> +		if (args->types == NULL)
> +			types[i] = args->recurrent_type;
> +		else
> +			types[i] = args->types[i];
> +	}

I don't understand what's going on here.

> +	types[argc] = field_type_MAX;
> +	sqlVdbeAddOp4(vdbe, OP_ApplyType, reg, argc, 0, (char *)types,
> +		      P4_DYNAMIC);
> +	if (args->is_blob_like_str)
> +		sqlVdbeChangeP5(vdbe, OPFLAG_BLOB_LIKE_STRING);
> +}
> +
>  /*
>   * Initialize a SelectDest structure.
>   */
>  void
>  sql_emit_table_types(struct Vdbe *v, struct space_def *def, int reg);
>  
> +/**
> + * Code an OP_ApplyType opcode that will force types for given

Not force, but try to cast implicitly. Force cast is implemented as CAST
operator.

> + * range of register starting from @a reg. These values then will
> + * be used as arguments of a function.
> + *
> + * @param vdbe VDBE.
> + * @param args Information about arguments of the function.
> + * @param reg Register where types will be placed.
> + * @param argc Number of arguments.
> + */
> +void
> +sql_emit_func_types(struct Vdbe *vdbe, struct sql_builtin_func_args *args,
> +		    int reg, uint32_t argc);
> +
>  enum field_type
>  sql_type_result(enum field_type lhs, enum field_type rhs);
>  
> @@ -4406,6 +4424,20 @@ struct sql_builtin_func_args {
>  	uint32_t min_count;
>  	/** Max number of arguments. */
>  	uint32_t max_count;
> +	/**
> +	 * If function arguments may not be of the same type, all
> +	 * argument types are described here.
> +	 */
> +	enum field_type *types;
> +	/**
> +	 * Contains the type of arguments if all arguments to the
> +	 * function are of the same type.
> +	 */
> +	enum field_type recurrent_type;

Recurrent is quite unsuitable name. Why not always use array
of type? Is it big deal to fill and store it?

> +	/**
> +	 * TRUE if the function should treat the BLOB as STRING.
> +	 */
> +	bool is_blob_like_str;

Why do you need this attribute?

>  };
>  
>  struct func_sql_builtin {
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 863f38f5d..a4bf84bcc 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -2882,7 +2882,7 @@ case OP_Fetch: {
>  	break;
>  }
>  
> -/* Opcode: ApplyType P1 P2 * P4 *
> +/* Opcode: ApplyType P1 P2 * P4 P5
>   * Synopsis: type(r[P1@P2])
>   *
>   * Check that types of P2 registers starting from register P1 are
> @@ -2890,6 +2890,9 @@ case OP_Fetch: {
>   * value and the given type are incompatible according to
>   * field_mp_plain_type_is_compatible(), but both are numeric,
>   * this opcode attempts to convert the value to the type.
> + *
> + * If P5 contains the OPFLAG_BLOB_LIKE_STRING flag, the BLOB
> + * values ​​are processed as if they had the field type STRING.

I see this unprintable symbols for the third time. Did you review
your own patch?


>  case OP_ApplyType: {
>  	enum field_type *types = pOp->p4.types;
> @@ -2904,6 +2907,13 @@ case OP_ApplyType: {
>  			pIn1++;
>  			continue;
>  		}

Need more info for this chunk.

> +		if ((pOp->p5 & OPFLAG_BLOB_LIKE_STRING) != 0) {
> +			if (type == FIELD_TYPE_STRING &&
> +			    mem_mp_type(pIn1) == MP_BIN) {
> +				pIn1++;
> +				continue;
> +			}
> +		}
>  		if (!mp_type_is_numeric(mem_mp_type(pIn1)) ||
>  		    !sql_type_is_numeric(type) ||
>  		    mem_convert_to_numeric(pIn1, type, false) != 0) {
> diff --git a/test/sql-tap/cse.test.lua b/test/sql-tap/cse.test.lua
> index 341b6de01..3c2076a1d 100755
> --- a/test/sql-tap/cse.test.lua
> +++ b/test/sql-tap/cse.test.lua
> @@ -195,23 +195,23 @@ test:do_execsql_test(
>  
>  
>  
> -test:do_execsql_test(
> +test:do_catchsql_test(
>      "cse-1.13",
>      [[
>          SELECT upper(b), typeof(b), b FROM t1
>      ]], {
>          -- <cse-1.13>
> -        "11", "integer", 11, "21", "integer", 21
> +        1, "Type mismatch: can not convert 11 to string"
>          -- </cse-1.13>
>      })

DId not look at test changes yet. Please, elaborate on notes above
firstly.

>  

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2020-07-13 14:56 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-07-13  5:32 [Tarantool-patches] [PATCH v4 0/5] Change implicit cast for assignment imeevma
2020-07-13  5:32 ` [Tarantool-patches] [PATCH v4 1/5] sql: set field_type in mem_set_*() functions imeevma
2020-07-13 10:36   ` Nikita Pettik
2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 2/5] sql: move diag setting to sql_func_by_signature() imeevma
2020-07-13 10:58   ` Nikita Pettik
2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 3/5] sql: check number of arguments in sql_func_by_signature() imeevma
2020-07-13 12:21   ` Nikita Pettik
2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 4/5] sql: change implicit cast for assignment imeevma
2020-07-13 14:42   ` Nikita Pettik
2020-07-13  5:33 ` [Tarantool-patches] [PATCH v4 5/5] sql: properly check arguments of functions imeevma
2020-07-13 14:56   ` Nikita Pettik

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox