Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL
@ 2019-04-14 15:03 Nikita Pettik
  2019-04-14 15:03 ` [tarantool-patches] [PATCH 1/9] sql: refactor mem_apply_numeric_type() Nikita Pettik
                   ` (10 more replies)
  0 siblings, 11 replies; 42+ messages in thread
From: Nikita Pettik @ 2019-04-14 15:03 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

Branch: https://github.com/tarantool/tarantool/tree/np/gh-3648-sql-introduce-bool
Issue: https://github.com/tarantool/tarantool/issues/3648
Issue: https://github.com/tarantool/tarantool/issues/3723

This patch-set makes type BOOLEAN available in SQL.
It consists of two main parts: first one itroduces basic boolean
capabilities like new literals, column type, insert/select etc;
second one makes predicates and search condition used in WHERE
and JOIN clauses accept and return boolean values.

For ANSI compliance see thread in @dev with subject "[dev] Boolean type in SQL"

Nikita Pettik (9):
  sql: refactor mem_apply_numeric_type()
  sql: disallow text values participate in sum() aggregate
  sql: use msgpack types instead of custom ones
  sql: introduce type boolean
  sql: improve type determination for column meta
  sql: make comparison predicate return boolean
  sql: make predicates accept and return boolean
  sql: make LIKE predicate return boolean result
  sql: make <search condition> accept only boolean

 extra/mkkeywordhash.c                              |   5 +
 src/box/bind.c                                     |  37 +-
 src/box/execute.c                                  |  21 +-
 src/box/lua/execute.c                              |   7 +-
 src/box/lua/lua_sql.c                              |  15 +-
 src/box/sql/build.c                                |  11 +-
 src/box/sql/date.c                                 |   2 +-
 src/box/sql/expr.c                                 | 107 ++--
 src/box/sql/func.c                                 | 155 +++---
 src/box/sql/legacy.c                               |   2 +-
 src/box/sql/parse.y                                |  19 +-
 src/box/sql/parse_def.c                            |   5 +
 src/box/sql/select.c                               |   4 +-
 src/box/sql/sqlInt.h                               |  31 +-
 src/box/sql/vdbe.c                                 | 154 +++---
 src/box/sql/vdbeInt.h                              |  24 +-
 src/box/sql/vdbeapi.c                              |  88 ++-
 src/box/sql/vdbeaux.c                              |  31 +-
 src/box/sql/vdbemem.c                              |  81 ++-
 src/box/sql/where.c                                |   3 +-
 src/box/sql/wherecode.c                            |   3 +-
 src/box/sql/whereexpr.c                            |   2 +-
 test/sql-tap/aggnested.test.lua                    |  10 +-
 test/sql-tap/cse.test.lua                          |   6 +-
 test/sql-tap/e_delete.test.lua                     |   8 +-
 test/sql-tap/e_select1.test.lua                    |  54 +-
 test/sql-tap/func.test.lua                         |   2 +-
 .../gh-3251-string-pattern-comparison.test.lua     |  76 +--
 test/sql-tap/identifier_case.test.lua              |  10 +-
 test/sql-tap/in1.test.lua                          |   8 +-
 test/sql-tap/in3.test.lua                          |   2 +-
 test/sql-tap/join5.test.lua                        |  20 +-
 test/sql-tap/limit.test.lua                        |   2 +-
 test/sql-tap/misc3.test.lua                        |   2 +-
 test/sql-tap/resolver01.test.lua                   |   2 +-
 test/sql-tap/select1.test.lua                      |   4 +-
 test/sql-tap/select2.test.lua                      |   8 +-
 test/sql-tap/select4.test.lua                      |   2 +-
 test/sql-tap/select5.test.lua                      |   3 +-
 test/sql-tap/select6.test.lua                      |   4 +-
 test/sql-tap/select9.test.lua                      |   6 +-
 test/sql-tap/subquery.test.lua                     |   4 +-
 test/sql-tap/tkt2832.test.lua                      |   2 +-
 test/sql-tap/tkt3346.test.lua                      |   2 +-
 test/sql-tap/tkt3541.test.lua                      |   2 +-
 test/sql-tap/whereG.test.lua                       |   6 +-
 test/sql/check-clear-ephemeral.result              |   2 +-
 test/sql/gh-2347-max-int-literals.result           |   2 +-
 test/sql/gh-3199-no-mem-leaks.result               |  18 +-
 test/sql/gh-3888-values-blob-assert.result         |   2 +-
 test/sql/persistency.result                        |   8 +-
 test/sql/transition.result                         |   4 +-
 test/sql/types.result                              | 605 ++++++++++++++++++++-
 test/sql/types.test.lua                            | 132 +++++
 54 files changed, 1362 insertions(+), 463 deletions(-)

-- 
2.15.1

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

* [tarantool-patches] [PATCH 1/9] sql: refactor mem_apply_numeric_type()
  2019-04-14 15:03 [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL Nikita Pettik
@ 2019-04-14 15:03 ` Nikita Pettik
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 2/9] sql: disallow text values participate in sum() aggregate Nikita Pettik
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 42+ messages in thread
From: Nikita Pettik @ 2019-04-14 15:03 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

Fix codestyle according to Tarantool guideline; remove unused argument
from signature; make function non-static - we are going to use it in
aggregate function sum() (src/box/sql/func.c) to attempt at converting
string values to numbers.
---
 src/box/sql/vdbe.c    | 49 +++++++++++++++++--------------------------------
 src/box/sql/vdbeInt.h | 15 +++++++++++++++
 2 files changed, 32 insertions(+), 32 deletions(-)

diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index ed7bf8870..1b3e2a59d 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -273,36 +273,21 @@ allocateCursor(
 	return pCx;
 }
 
-/*
- * Try to convert a value into a numeric representation if we can
- * do so without loss of information.  In other words, if the string
- * looks like a number, convert it into a number.  If it does not
- * look like a number, leave it alone.
- *
- * If the bTryForInt flag is true, then extra effort is made to give
- * an integer representation.  Strings that look like floating point
- * values but which have no fractional component (example: '48.00')
- * will have a MEM_Int representation when bTryForInt is true.
- *
- * If bTryForInt is false, then if the input string contains a decimal
- * point or exponential notation, the result is only MEM_Real, even
- * if there is an exact integer representation of the quantity.
- */
-static int
-mem_apply_numeric_type(Mem *pRec, int bTryForInt)
+int
+mem_apply_numeric_type(struct Mem *record)
 {
-	double rValue;
-	i64 iValue;
-	assert((pRec->flags & (MEM_Str|MEM_Int|MEM_Real))==MEM_Str);
-	if (sqlAtoF(pRec->z, &rValue, pRec->n) == 0) return -1;
-	if (0 == sql_atoi64(pRec->z, (int64_t *)&iValue, pRec->n)) {
-		pRec->u.i = iValue;
-		pRec->flags |= MEM_Int;
-	} else {
-		pRec->u.r = rValue;
-		pRec->flags |= MEM_Real;
-		if (bTryForInt) mem_apply_integer_type(pRec);
+	assert((record->flags & (MEM_Str | MEM_Int | MEM_Real)) == MEM_Str);
+	int64_t integer_value;
+	if (sql_atoi64(record->z, &integer_value, record->n) == 0) {
+		record->u.i = integer_value;
+		MemSetTypeFlag(record, MEM_Int);
+		return 0;
 	}
+	double float_value;
+	if (sqlAtoF(record->z, &float_value, record->n) == 0)
+		return -1;
+	record->u.r = float_value;
+	MemSetTypeFlag(record, MEM_Real);
 	return 0;
 }
 
@@ -380,7 +365,7 @@ int sql_value_numeric_type(sql_value *pVal) {
 	int eType = sql_value_type(pVal);
 	if (eType==SQL_TEXT) {
 		Mem *pMem = (Mem*)pVal;
-		mem_apply_numeric_type(pMem, 0);
+		mem_apply_numeric_type(pMem);
 		eType = sql_value_type(pVal);
 	}
 	return eType;
@@ -2196,12 +2181,12 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
 		if (sql_type_is_numeric(type)) {
 			if ((flags1 | flags3)&MEM_Str) {
 				if ((flags1 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str) {
-					mem_apply_numeric_type(pIn1, 0);
+					mem_apply_numeric_type(pIn1);
 					testcase( flags3!=pIn3->flags); /* Possible if pIn1==pIn3 */
 					flags3 = pIn3->flags;
 				}
 				if ((flags3 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str) {
-					if (mem_apply_numeric_type(pIn3, 0) != 0) {
+					if (mem_apply_numeric_type(pIn3) != 0) {
 						diag_set(ClientError,
 							 ER_SQL_TYPE_MISMATCH,
 							 sql_value_text(pIn3),
@@ -3550,7 +3535,7 @@ case OP_SeekGT: {       /* jump, in3 */
 		 */
 		pIn3 = &aMem[reg_ipk];
 		if ((pIn3->flags & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str) {
-			mem_apply_numeric_type(pIn3, 0);
+			mem_apply_numeric_type(pIn3);
 		}
 		int64_t i;
 		if ((pIn3->flags & MEM_Int) == MEM_Int) {
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index c84f22caf..8cd00d43a 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -269,6 +269,21 @@ enum {
 char *
 mem_type_to_str(const struct Mem *p);
 
+/**
+ * Try to convert a string value into a numeric representation
+ * if we can do so without loss of information. Firstly, value
+ * is attempted to be converted to integer, and in case of fail -
+ * to floating point number. Note that function is assumed to be
+ * called only on memory cell containing string,
+ * i.e. memory->type == MEM_Str.
+ *
+ * @param record Memory cell containing value to be converted.
+ * @retval 0 If value can be converted to integer or number.
+ * @retval -1 Otherwise.
+ */
+int
+mem_apply_numeric_type(struct Mem *record);
+
 /* Return TRUE if Mem X contains dynamically allocated content - anything
  * that needs to be deallocated to avoid a leak.
  */
-- 
2.15.1

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

* [tarantool-patches] [PATCH 2/9] sql: disallow text values participate in sum() aggregate
  2019-04-14 15:03 [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL Nikita Pettik
  2019-04-14 15:03 ` [tarantool-patches] [PATCH 1/9] sql: refactor mem_apply_numeric_type() Nikita Pettik
@ 2019-04-14 15:04 ` Nikita Pettik
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 3/9] sql: use msgpack types instead of custom ones Nikita Pettik
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 42+ messages in thread
From: Nikita Pettik @ 2019-04-14 15:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

It is obvious that we can't add string with number except the case when
string is a number literal in quotes (aka '123' or '0.5'). Before this
patch values which can't be converted to numbers were just skipped.
Now error is raised. Another one misbehavior was in using
sql_value_numeric_type() function, which set flag indicating number
value in memory cell, but didn't clear MEM_Str flag. As a result, we
couldn't determine type of value in such memory cell without
ambigiousness.
---
 src/box/sql/func.c            | 38 ++++++++++++++++++++++++--------------
 src/box/sql/sqlInt.h          |  3 ---
 src/box/sql/vdbe.c            | 19 ++-----------------
 src/box/sql/vdbeInt.h         |  3 +--
 test/sql-tap/select1.test.lua |  4 ++--
 test/sql-tap/select5.test.lua |  3 ++-
 6 files changed, 31 insertions(+), 39 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index b86a95d9a..9adfeec67 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1495,24 +1495,34 @@ static void
 sumStep(sql_context * context, int argc, sql_value ** argv)
 {
 	SumCtx *p;
-	int type;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
 	p = sql_aggregate_context(context, sizeof(*p));
-	type = sql_value_numeric_type(argv[0]);
-	if (p && type != SQL_NULL) {
-		p->cnt++;
-		if (type == SQL_INTEGER) {
-			i64 v = sql_value_int64(argv[0]);
-			p->rSum += v;
-			if ((p->approx | p->overflow) == 0
-			    && sqlAddInt64(&p->iSum, v)) {
-				p->overflow = 1;
-			}
-		} else {
-			p->rSum += sql_value_double(argv[0]);
-			p->approx = 1;
+	assert(p != NULL);
+	int type = sql_value_type(argv[0]);
+	if (type == SQL_NULL)
+		return;
+	if (type != SQL_FLOAT && type != SQL_INTEGER) {
+		if (mem_apply_numeric_type(argv[0]) != 0) {
+			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+				 sql_value_text(argv[0]), "number");
+			context->fErrorOrAux = 1;
+			context->isError = SQL_TARANTOOL_ERROR;
+			return;
 		}
+		type = sql_value_type(argv[0]);
+	}
+	p->cnt++;
+	if (type == SQL_INTEGER) {
+		i64 v = sql_value_int64(argv[0]);
+		p->rSum += v;
+		if ((p->approx | p->overflow) == 0 &&
+		    sqlAddInt64(&p->iSum, v)) {
+			p->overflow = 1;
+		}
+	} else {
+		p->rSum += sql_value_double(argv[0]);
+		p->approx = 1;
 	}
 }
 
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index d8dc03284..b5c596d2f 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -458,9 +458,6 @@ sql_value_text(sql_value *);
 int
 sql_value_type(sql_value *);
 
-int
-sql_value_numeric_type(sql_value *);
-
 sql *
 sql_context_db_handle(sql_context *);
 
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 1b3e2a59d..c689aaf1d 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -276,7 +276,8 @@ allocateCursor(
 int
 mem_apply_numeric_type(struct Mem *record)
 {
-	assert((record->flags & (MEM_Str | MEM_Int | MEM_Real)) == MEM_Str);
+	if ((record->flags & (MEM_Str | MEM_Int | MEM_Real)) != MEM_Str)
+		return -1;
 	int64_t integer_value;
 	if (sql_atoi64(record->z, &integer_value, record->n) == 0) {
 		record->u.i = integer_value;
@@ -355,22 +356,6 @@ mem_apply_type(struct Mem *record, enum field_type type)
 	}
 }
 
-/*
- * Try to convert the type of a function argument or a result column
- * into a numeric representation.  Use either INTEGER or REAL whichever
- * is appropriate.  But only do the conversion if it is possible without
- * loss of information and return the revised type of the argument.
- */
-int sql_value_numeric_type(sql_value *pVal) {
-	int eType = sql_value_type(pVal);
-	if (eType==SQL_TEXT) {
-		Mem *pMem = (Mem*)pVal;
-		mem_apply_numeric_type(pMem);
-		eType = sql_value_type(pVal);
-	}
-	return eType;
-}
-
 /*
  * Exported version of mem_apply_type(). This one works on sql_value*,
  * not the internal Mem* type.
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 8cd00d43a..ec9123a66 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -274,8 +274,7 @@ mem_type_to_str(const struct Mem *p);
  * if we can do so without loss of information. Firstly, value
  * is attempted to be converted to integer, and in case of fail -
  * to floating point number. Note that function is assumed to be
- * called only on memory cell containing string,
- * i.e. memory->type == MEM_Str.
+ * called on memory cell containing string, i.e. mem->type == MEM_Str.
  *
  * @param record Memory cell containing value to be converted.
  * @retval 0 If value can be converted to integer or number.
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index be63e815d..1bad7679f 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -503,13 +503,13 @@ test:do_catchsql_test(
         -- </select1-2.17>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "select1-2.17.1",
     [[
         SELECT sum(a) FROM t3
     ]], {
         -- <select1-2.17.1>
-        44.0
+        1, "Type mismatch: can not convert abc to number"
         -- </select1-2.17.1>
     })
 
diff --git a/test/sql-tap/select5.test.lua b/test/sql-tap/select5.test.lua
index 507448d00..1353b39fc 100755
--- a/test/sql-tap/select5.test.lua
+++ b/test/sql-tap/select5.test.lua
@@ -550,7 +550,7 @@ test:do_execsql_test(
     -- </select5-9.13>
 })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "select5-9.13.2",
     [[
             CREATE TABLE jj (s1 INT, s2 VARCHAR(1), PRIMARY KEY(s1));
@@ -558,6 +558,7 @@ test:do_execsql_test(
             SELECT 1 FROM jj HAVING avg(s2) = 1 AND avg(s2) = 0;
     ]], {
     -- <select5-9.13.2>
+    1, "Type mismatch: can not convert A to number"
     -- </select5-9.13.2>
 })
 
-- 
2.15.1

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

* [tarantool-patches] [PATCH 3/9] sql: use msgpack types instead of custom ones
  2019-04-14 15:03 [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL Nikita Pettik
  2019-04-14 15:03 ` [tarantool-patches] [PATCH 1/9] sql: refactor mem_apply_numeric_type() Nikita Pettik
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 2/9] sql: disallow text values participate in sum() aggregate Nikita Pettik
@ 2019-04-14 15:04 ` Nikita Pettik
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 4/9] sql: introduce type boolean Nikita Pettik
                   ` (7 subsequent siblings)
  10 siblings, 1 reply; 42+ messages in thread
From: Nikita Pettik @ 2019-04-14 15:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

This patch provides straightforward refactoring replacing enum sql_type
with enum mp_type. Note that we use msgpack format instead of field_type
since it is more suitable when dealing with NULLs.
---
 src/box/bind.c          |  37 ++++++------------
 src/box/execute.c       |  12 +++---
 src/box/lua/execute.c   |   7 +---
 src/box/lua/lua_sql.c   |  12 +++---
 src/box/sql/date.c      |   2 +-
 src/box/sql/func.c      | 100 ++++++++++++++++++++++++------------------------
 src/box/sql/legacy.c    |   2 +-
 src/box/sql/sqlInt.h    |  16 ++------
 src/box/sql/vdbeapi.c   |  66 ++++++++++----------------------
 src/box/sql/whereexpr.c |   2 +-
 10 files changed, 102 insertions(+), 154 deletions(-)

diff --git a/src/box/bind.c b/src/box/bind.c
index 4b57e23c8..5aa79751a 100644
--- a/src/box/bind.c
+++ b/src/box/bind.c
@@ -35,15 +35,6 @@
 #include "sql/sqlLimit.h"
 #include "sql/vdbe.h"
 
-const char *sql_type_strs[] = {
-	NULL,
-	"INTEGER",
-	"FLOAT",
-	"TEXT",
-	"BLOB",
-	"NULL",
-};
-
 const char *
 sql_bind_name(const struct sql_bind *bind)
 {
@@ -74,7 +65,8 @@ sql_bind_decode(struct sql_bind *bind, int i, const char **packet)
 		bind->name = NULL;
 		bind->name_len = 0;
 	}
-	switch (mp_typeof(**packet)) {
+	enum mp_type type = mp_typeof(**packet);
+	switch (type) {
 	case MP_UINT: {
 		uint64_t n = mp_decode_uint(packet);
 		if (n > INT64_MAX) {
@@ -83,49 +75,40 @@ sql_bind_decode(struct sql_bind *bind, int i, const char **packet)
 			return -1;
 		}
 		bind->i64 = (int64_t) n;
-		bind->type = SQL_INTEGER;
 		bind->bytes = sizeof(bind->i64);
 		break;
 	}
 	case MP_INT:
 		bind->i64 = mp_decode_int(packet);
-		bind->type = SQL_INTEGER;
 		bind->bytes = sizeof(bind->i64);
 		break;
 	case MP_STR:
 		bind->s = mp_decode_str(packet, &bind->bytes);
-		bind->type = SQL_TEXT;
 		break;
 	case MP_DOUBLE:
 		bind->d = mp_decode_double(packet);
-		bind->type = SQL_FLOAT;
 		bind->bytes = sizeof(bind->d);
 		break;
 	case MP_FLOAT:
 		bind->d = mp_decode_float(packet);
-		bind->type = SQL_FLOAT;
 		bind->bytes = sizeof(bind->d);
 		break;
 	case MP_NIL:
 		mp_decode_nil(packet);
-		bind->type = SQL_NULL;
 		bind->bytes = 1;
 		break;
 	case MP_BOOL:
 		/* sql doesn't support boolean. Use int instead. */
 		bind->i64 = mp_decode_bool(packet) ? 1 : 0;
-		bind->type = SQL_INTEGER;
 		bind->bytes = sizeof(bind->i64);
 		break;
 	case MP_BIN:
 		bind->s = mp_decode_bin(packet, &bind->bytes);
-		bind->type = SQL_BLOB;
 		break;
 	case MP_EXT:
 		bind->s = *packet;
 		mp_next(packet);
 		bind->bytes = *packet - bind->s;
-		bind->type = SQL_BLOB;
 		break;
 	case MP_ARRAY:
 		diag_set(ClientError, ER_SQL_BIND_TYPE, "ARRAY",
@@ -138,6 +121,7 @@ sql_bind_decode(struct sql_bind *bind, int i, const char **packet)
 	default:
 		unreachable();
 	}
+	bind->type = type;
 	return 0;
 }
 
@@ -189,13 +173,16 @@ sql_bind_column(struct sql_stmt *stmt, const struct sql_bind *p,
 		}
 	}
 	switch (p->type) {
-	case SQL_INTEGER:
+	case MP_INT:
+	case MP_UINT:
+	case MP_BOOL:
 		rc = sql_bind_int64(stmt, pos, p->i64);
 		break;
-	case SQL_FLOAT:
+	case MP_DOUBLE:
+	case MP_FLOAT:
 		rc = sql_bind_double(stmt, pos, p->d);
 		break;
-	case SQL_TEXT:
+	case MP_STR:
 		/*
 		 * Parameters are allocated within message pack,
 		 * received from the iproto thread. IProto thread
@@ -207,10 +194,10 @@ sql_bind_column(struct sql_stmt *stmt, const struct sql_bind *p,
 		rc = sql_bind_text64(stmt, pos, p->s, p->bytes,
 					 SQL_STATIC);
 		break;
-	case SQL_NULL:
+	case MP_NIL:
 		rc = sql_bind_null(stmt, pos);
 		break;
-	case SQL_BLOB:
+	case MP_BIN:
 		rc = sql_bind_blob64(stmt, pos, (const void *) p->s,
 					 p->bytes, SQL_STATIC);
 		break;
@@ -227,7 +214,7 @@ sql_bind_column(struct sql_stmt *stmt, const struct sql_bind *p,
 	case SQL_TOOBIG:
 	default:
 		diag_set(ClientError, ER_SQL_BIND_VALUE, sql_bind_name(p),
-			 sql_type_strs[p->type]);
+			 mp_type_strs[p->type]);
 		break;
 	}
 	return -1;
diff --git a/src/box/execute.c b/src/box/execute.c
index af2182867..2da2bc88b 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -133,9 +133,9 @@ sql_column_to_messagepack(struct sql_stmt *stmt, int i,
 			  struct region *region)
 {
 	size_t size;
-	int type = sql_column_type(stmt, i);
+	enum mp_type type = sql_column_type(stmt, i);
 	switch (type) {
-	case SQL_INTEGER: {
+	case MP_INT: {
 		int64_t n = sql_column_int64(stmt, i);
 		if (n >= 0)
 			size = mp_sizeof_uint(n);
@@ -150,7 +150,7 @@ sql_column_to_messagepack(struct sql_stmt *stmt, int i,
 			mp_encode_int(pos, n);
 		break;
 	}
-	case SQL_FLOAT: {
+	case MP_DOUBLE: {
 		double d = sql_column_double(stmt, i);
 		size = mp_sizeof_double(d);
 		char *pos = (char *) region_alloc(region, size);
@@ -159,7 +159,7 @@ sql_column_to_messagepack(struct sql_stmt *stmt, int i,
 		mp_encode_double(pos, d);
 		break;
 	}
-	case SQL_TEXT: {
+	case MP_STR: {
 		uint32_t len = sql_column_bytes(stmt, i);
 		size = mp_sizeof_str(len);
 		char *pos = (char *) region_alloc(region, size);
@@ -170,7 +170,7 @@ sql_column_to_messagepack(struct sql_stmt *stmt, int i,
 		mp_encode_str(pos, s, len);
 		break;
 	}
-	case SQL_BLOB: {
+	case MP_BIN: {
 		uint32_t len = sql_column_bytes(stmt, i);
 		const char *s =
 			(const char *)sql_column_blob(stmt, i);
@@ -189,7 +189,7 @@ sql_column_to_messagepack(struct sql_stmt *stmt, int i,
 		}
 		break;
 	}
-	case SQL_NULL: {
+	case MP_NIL: {
 		size = mp_sizeof_nil();
 		char *pos = (char *) region_alloc(region, size);
 		if (pos == NULL)
diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c
index 71d64bc27..45d3fc949 100644
--- a/src/box/lua/execute.c
+++ b/src/box/lua/execute.c
@@ -146,7 +146,6 @@ lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i)
 		FALLTHROUGH;
 	case MP_INT:
 		bind->i64 = field.ival;
-		bind->type = SQL_INTEGER;
 		bind->bytes = sizeof(bind->i64);
 		break;
 	case MP_STR:
@@ -162,28 +161,23 @@ lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i)
 		}
 		memcpy(buf, field.sval.data, field.sval.len + 1);
 		bind->s = buf;
-		bind->type = SQL_TEXT;
 		bind->bytes = field.sval.len;
 		break;
 	case MP_DOUBLE:
 	case MP_FLOAT:
 		bind->d = field.dval;
-		bind->type = SQL_FLOAT;
 		bind->bytes = sizeof(bind->d);
 		break;
 	case MP_NIL:
-		bind->type = SQL_NULL;
 		bind->bytes = 1;
 		break;
 	case MP_BOOL:
 		/* SQLite doesn't support boolean. Use int instead. */
 		bind->i64 = field.bval ? 1 : 0;
-		bind->type = SQL_INTEGER;
 		bind->bytes = sizeof(bind->i64);
 		break;
 	case MP_BIN:
 		bind->s = mp_decode_bin(&field.sval.data, &bind->bytes);
-		bind->type = SQL_BLOB;
 		break;
 	case MP_EXT:
 		diag_set(ClientError, ER_SQL_BIND_TYPE, "USERDATA",
@@ -200,6 +194,7 @@ lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i)
 	default:
 		unreachable();
 	}
+	bind->type = field.type;
 	lua_pop(L, lua_gettop(L) - idx);
 	return 0;
 }
diff --git a/src/box/lua/lua_sql.c b/src/box/lua/lua_sql.c
index 3d0047e16..239ed1b2f 100644
--- a/src/box/lua/lua_sql.c
+++ b/src/box/lua/lua_sql.c
@@ -56,21 +56,21 @@ lua_sql_call(sql_context *pCtx, int nVal, sql_value **apVal) {
 	lua_rawgeti(L, LUA_REGISTRYINDEX, func_info->func_ref);
 	for (int i = 0; i < nVal; i++) {
 		sql_value *param = apVal[i];
-		switch (sql_value_type(param)) {
-		case SQL_INTEGER:
+		switch (sql_value_mp_type(param)) {
+		case MP_INT:
 			luaL_pushint64(L, sql_value_int64(param));
 			break;
-		case SQL_FLOAT:
+		case MP_DOUBLE:
 			lua_pushnumber(L, sql_value_double(param));
 			break;
-		case SQL_TEXT:
+		case MP_STR:
 			lua_pushstring(L, (const char *) sql_value_text(param));
 			break;
-		case SQL_BLOB:
+		case MP_BIN:
 			lua_pushlstring(L, sql_value_blob(param),
 					(size_t) sql_value_bytes(param));
 			break;
-		case SQL_NULL:
+		case MP_NIL:
 			lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_nil_ref);
 			break;
 		default:
diff --git a/src/box/sql/date.c b/src/box/sql/date.c
index 5f5272ea3..ead0c01d0 100644
--- a/src/box/sql/date.c
+++ b/src/box/sql/date.c
@@ -937,7 +937,7 @@ isDate(sql_context * context, int argc, sql_value ** argv, DateTime * p)
 	if (argc == 0) {
 		return setDateTimeToCurrent(context, p);
 	}
-	if ((eType = sql_value_type(argv[0])) == SQL_FLOAT
+	if ((eType = sql_value_type(argv[0])) == MP_DOUBLE
 	    || eType == SQL_INTEGER) {
 		setRawDateNumber(p, sql_value_double(argv[0]));
 	} else {
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 9adfeec67..3cdb119c8 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -87,10 +87,10 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 	pColl = sqlGetFuncCollSeq(context);
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
-	if (sql_value_type(argv[0]) == SQL_NULL)
+	if (sql_value_mp_type(argv[0]) == MP_NIL)
 		return;
 	for (i = 1; i < argc; i++) {
-		if (sql_value_type(argv[i]) == SQL_NULL)
+		if (sql_value_mp_type(argv[i]) == MP_NIL)
 			return;
 		if ((sqlMemCompare(argv[iBest], argv[i], pColl) ^ mask) >=
 		    0) {
@@ -109,17 +109,17 @@ typeofFunc(sql_context * context, int NotUsed, sql_value ** argv)
 {
 	const char *z = 0;
 	UNUSED_PARAMETER(NotUsed);
-	switch (sql_value_type(argv[0])) {
-	case SQL_INTEGER:
+	switch (sql_value_mp_type(argv[0])) {
+	case MP_INT:
 		z = "integer";
 		break;
-	case SQL_TEXT:
+	case MP_STR:
 		z = "text";
 		break;
-	case SQL_FLOAT:
+	case MP_DOUBLE:
 		z = "real";
 		break;
-	case SQL_BLOB:
+	case MP_BIN:
 		z = "blob";
 		break;
 	default:
@@ -139,15 +139,15 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
 
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	switch (sql_value_type(argv[0])) {
-	case SQL_BLOB:
-	case SQL_INTEGER:
-	case SQL_FLOAT:{
+	switch (sql_value_mp_type(argv[0])) {
+	case MP_BIN:
+	case MP_INT:
+	case MP_DOUBLE:{
 			sql_result_int(context,
 					   sql_value_bytes(argv[0]));
 			break;
 		}
-	case SQL_TEXT:{
+	case MP_STR:{
 			const unsigned char *z = sql_value_text(argv[0]);
 			if (z == 0)
 				return;
@@ -173,8 +173,8 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 {
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	switch (sql_value_type(argv[0])) {
-	case SQL_INTEGER:{
+	switch (sql_value_mp_type(argv[0])) {
+	case MP_INT:{
 			i64 iVal = sql_value_int64(argv[0]);
 			if (iVal < 0) {
 				if (iVal == SMALLEST_INT64) {
@@ -192,7 +192,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 			sql_result_int64(context, iVal);
 			break;
 		}
-	case SQL_NULL:{
+	case MP_NIL:{
 			/* IMP: R-37434-19929 Abs(X) returns NULL if X is NULL. */
 			sql_result_null(context);
 			break;
@@ -229,19 +229,19 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
 	UNUSED_PARAMETER(argc);
 	struct Mem *needle = argv[0];
 	struct Mem *haystack = argv[1];
-	int needle_type = sql_value_type(needle);
-	int haystack_type = sql_value_type(haystack);
+	enum mp_type needle_type = sql_value_mp_type(needle);
+	enum mp_type haystack_type = sql_value_mp_type(haystack);
 
-	if (haystack_type == SQL_NULL || needle_type == SQL_NULL)
+	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 != SQL_TEXT && needle_type != SQL_BLOB)
+	if (needle_type != MP_STR && needle_type != MP_BIN)
 		inconsistent_type_arg = needle;
-	if (haystack_type != SQL_TEXT && haystack_type != SQL_BLOB)
+	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 BLOB",
@@ -268,7 +268,7 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
 	if (n_needle_bytes > 0) {
 		const unsigned char *haystack_str;
 		const unsigned char *needle_str;
-		if (haystack_type == SQL_BLOB) {
+		if (haystack_type == MP_BIN) {
 			needle_str = sql_value_blob(needle);
 			haystack_str = sql_value_blob(haystack);
 			assert(needle_str != NULL);
@@ -398,14 +398,14 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	int negP2 = 0;
 
 	assert(argc == 3 || argc == 2);
-	if (sql_value_type(argv[1]) == SQL_NULL
-	    || (argc == 3 && sql_value_type(argv[2]) == SQL_NULL)
+	if (sql_value_mp_type(argv[1]) == MP_NIL
+	    || (argc == 3 && sql_value_mp_type(argv[2]) == MP_NIL)
 	    ) {
 		return;
 	}
-	p0type = sql_value_type(argv[0]);
+	p0type = sql_value_mp_type(argv[0]);
 	p1 = sql_value_int(argv[1]);
-	if (p0type == SQL_BLOB) {
+	if (p0type == MP_BIN) {
 		len = sql_value_bytes(argv[0]);
 		z = sql_value_blob(argv[0]);
 		if (z == 0)
@@ -460,7 +460,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 		}
 	}
 	assert(p1 >= 0 && p2 >= 0);
-	if (p0type != SQL_BLOB) {
+	if (p0type != MP_BIN) {
 		/*
 		 * In the code below 'cnt' and 'n_chars' is
 		 * used because '\0' is not supposed to be
@@ -507,13 +507,13 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 	char *zBuf;
 	assert(argc == 1 || argc == 2);
 	if (argc == 2) {
-		if (SQL_NULL == sql_value_type(argv[1]))
+		if (MP_NIL == sql_value_mp_type(argv[1]))
 			return;
 		n = sql_value_int(argv[1]);
 		if (n < 0)
 			n = 0;
 	}
-	if (sql_value_type(argv[0]) == SQL_NULL)
+	if (sql_value_mp_type(argv[0]) == MP_NIL)
 		return;
 	r = sql_value_double(argv[0]);
 	/* If Y==0 and X will fit in a 64-bit int,
@@ -900,13 +900,13 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 	int nPat;
 	sql *db = sql_context_db_handle(context);
 	int is_like_ci = SQL_PTR_TO_INT(sql_user_data(context));
-	int rhs_type = sql_value_type(argv[0]);
-	int lhs_type = sql_value_type(argv[1]);
+	int rhs_type = sql_value_mp_type(argv[0]);
+	int lhs_type = sql_value_mp_type(argv[1]);
 
-	if (lhs_type != SQL_TEXT || rhs_type != SQL_TEXT) {
-		if (lhs_type == SQL_NULL || rhs_type == SQL_NULL)
+	if (lhs_type != MP_STR || rhs_type != MP_STR) {
+		if (lhs_type == MP_NIL || rhs_type == MP_NIL)
 			return;
-		char *inconsistent_type = rhs_type != SQL_TEXT ?
+		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",
@@ -1018,8 +1018,8 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 {
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	switch (sql_value_type(argv[0])) {
-	case SQL_FLOAT:{
+	switch (sql_value_mp_type(argv[0])) {
+	case MP_DOUBLE:{
 			double r1, r2;
 			char zBuf[50];
 			r1 = sql_value_double(argv[0]);
@@ -1033,11 +1033,11 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 					    SQL_TRANSIENT);
 			break;
 		}
-	case SQL_INTEGER:{
+	case MP_INT:{
 			sql_result_value(context, argv[0]);
 			break;
 		}
-	case SQL_BLOB:{
+	case MP_BIN:{
 			char *zText = 0;
 			char const *zBlob = sql_value_blob(argv[0]);
 			int nBlob = sql_value_bytes(argv[0]);
@@ -1063,7 +1063,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 			}
 			break;
 		}
-	case SQL_TEXT:{
+	case MP_STR:{
 			int i, j;
 			u64 n;
 			const unsigned char *zArg = sql_value_text(argv[0]);
@@ -1092,7 +1092,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 			break;
 		}
 	default:{
-			assert(sql_value_type(argv[0]) == SQL_NULL);
+			assert(sql_value_mp_type(argv[0]) == MP_NIL);
 			sql_result_text(context, "NULL", 4, SQL_STATIC);
 			break;
 		}
@@ -1228,13 +1228,13 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 	assert(zStr == sql_value_text(argv[0]));	/* No encoding change */
 	zPattern = sql_value_text(argv[1]);
 	if (zPattern == 0) {
-		assert(sql_value_type(argv[1]) == SQL_NULL
+		assert(sql_value_mp_type(argv[1]) == MP_NIL
 		       || sql_context_db_handle(context)->mallocFailed);
 		return;
 	}
 	nPattern = sql_value_bytes(argv[1]);
 	if (nPattern == 0) {
-		assert(sql_value_type(argv[1]) != SQL_NULL);
+		assert(sql_value_mp_type(argv[1]) != MP_NIL);
 		sql_result_value(context, argv[0]);
 		return;
 	}
@@ -1302,7 +1302,7 @@ trimFunc(sql_context * context, int argc, sql_value ** argv)
 	unsigned char **azChar = 0;	/* Individual characters in zCharSet */
 	int nChar;		/* Number of characters in zCharSet */
 
-	if (sql_value_type(argv[0]) == SQL_NULL) {
+	if (sql_value_mp_type(argv[0]) == MP_NIL) {
 		return;
 	}
 	zIn = sql_value_text(argv[0]);
@@ -1499,10 +1499,10 @@ sumStep(sql_context * context, int argc, sql_value ** argv)
 	UNUSED_PARAMETER(argc);
 	p = sql_aggregate_context(context, sizeof(*p));
 	assert(p != NULL);
-	int type = sql_value_type(argv[0]);
-	if (type == SQL_NULL)
+	enum mp_type type = sql_value_mp_type(argv[0]);
+	if (type == MP_NIL)
 		return;
-	if (type != SQL_FLOAT && type != SQL_INTEGER) {
+	if (type != MP_DOUBLE && type != MP_INT) {
 		if (mem_apply_numeric_type(argv[0]) != 0) {
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
 				 sql_value_text(argv[0]), "number");
@@ -1510,10 +1510,10 @@ sumStep(sql_context * context, int argc, sql_value ** argv)
 			context->isError = SQL_TARANTOOL_ERROR;
 			return;
 		}
-		type = sql_value_type(argv[0]);
+		type = sql_value_mp_type(argv[0]);
 	}
 	p->cnt++;
-	if (type == SQL_INTEGER) {
+	if (type == MP_INT) {
 		i64 v = sql_value_int64(argv[0]);
 		p->rSum += v;
 		if ((p->approx | p->overflow) == 0 &&
@@ -1578,7 +1578,7 @@ countStep(sql_context * context, int argc, sql_value ** argv)
 {
 	CountCtx *p;
 	p = sql_aggregate_context(context, sizeof(*p));
-	if ((argc == 0 || SQL_NULL != sql_value_type(argv[0])) && p) {
+	if ((argc == 0 || MP_NIL != sql_value_mp_type(argv[0])) && p) {
 		p->n++;
 	}
 }
@@ -1605,7 +1605,7 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 	if (!pBest)
 		return;
 
-	if (sql_value_type(argv[0]) == SQL_NULL) {
+	if (sql_value_mp_type(argv[0]) == MP_NIL) {
 		if (pBest->flags)
 			sqlSkipAccumulatorLoad(context);
 	} else if (pBest->flags) {
@@ -1657,7 +1657,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 	const char *zSep;
 	int nVal, nSep;
 	assert(argc == 1 || argc == 2);
-	if (sql_value_type(argv[0]) == SQL_NULL)
+	if (sql_value_mp_type(argv[0]) == MP_NIL)
 		return;
 	pAccum =
 	    (StrAccum *) sql_aggregate_context(context, sizeof(*pAccum));
diff --git a/src/box/sql/legacy.c b/src/box/sql/legacy.c
index 599fba0be..64b1564ee 100644
--- a/src/box/sql/legacy.c
+++ b/src/box/sql/legacy.c
@@ -130,7 +130,7 @@ sql_exec(sql * db,	/* The database on which the SQL executes */
 						    &&
 						    sql_column_type(pStmt,
 									i) !=
-						    SQL_NULL) {
+						    MP_NIL) {
 							sqlOomFault(db);
 							goto exec_out;
 						}
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index b5c596d2f..9134f767d 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -455,8 +455,8 @@ sql_value_int64(sql_value *);
 const unsigned char *
 sql_value_text(sql_value *);
 
-int
-sql_value_type(sql_value *);
+enum mp_type
+sql_value_mp_type(sql_value *);
 
 sql *
 sql_context_db_handle(sql_context *);
@@ -580,8 +580,8 @@ const unsigned char *
 sql_column_text(sql_stmt *,
 		    int iCol);
 
-int
-sql_column_type(sql_stmt *, int iCol);
+enum mp_type
+sql_column_type(sql_stmt *stmt, int field_no);
 
 sql_value *
 sql_column_value(sql_stmt *,
@@ -632,14 +632,6 @@ sql_exec(sql *,	/* An open database */
 #define SQL_CONSTRAINT_TRIGGER      (SQL_CONSTRAINT | (7<<8))
 #define SQL_CONSTRAINT_UNIQUE       (SQL_CONSTRAINT | (8<<8))
 
-enum sql_type {
-	SQL_INTEGER = 1,
-	SQL_FLOAT = 2,
-	SQL_TEXT = 3,
-	SQL_BLOB = 4,
-	SQL_NULL = 5,
-};
-
 /**
  * Subtype of a main type. Allows to do some subtype specific
  * things: serialization, unpacking etc.
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index be5c9dff9..6e867ca84 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -243,44 +243,18 @@ sql_value_text(sql_value * pVal)
  * fundamental datatypes: 64-bit signed integer 64-bit IEEE floating
  * point number string BLOB NULL
  */
-int
-sql_value_type(sql_value * pVal)
-{
-	static const u8 aType[] = {
-		SQL_BLOB,	/* 0x00 */
-		SQL_NULL,	/* 0x01 */
-		SQL_TEXT,	/* 0x02 */
-		SQL_NULL,	/* 0x03 */
-		SQL_INTEGER,	/* 0x04 */
-		SQL_NULL,	/* 0x05 */
-		SQL_INTEGER,	/* 0x06 */
-		SQL_NULL,	/* 0x07 */
-		SQL_FLOAT,	/* 0x08 */
-		SQL_NULL,	/* 0x09 */
-		SQL_FLOAT,	/* 0x0a */
-		SQL_NULL,	/* 0x0b */
-		SQL_INTEGER,	/* 0x0c */
-		SQL_NULL,	/* 0x0d */
-		SQL_INTEGER,	/* 0x0e */
-		SQL_NULL,	/* 0x0f */
-		SQL_BLOB,	/* 0x10 */
-		SQL_NULL,	/* 0x11 */
-		SQL_TEXT,	/* 0x12 */
-		SQL_NULL,	/* 0x13 */
-		SQL_INTEGER,	/* 0x14 */
-		SQL_NULL,	/* 0x15 */
-		SQL_INTEGER,	/* 0x16 */
-		SQL_NULL,	/* 0x17 */
-		SQL_FLOAT,	/* 0x18 */
-		SQL_NULL,	/* 0x19 */
-		SQL_FLOAT,	/* 0x1a */
-		SQL_NULL,	/* 0x1b */
-		SQL_INTEGER,	/* 0x1c */
-		SQL_NULL,	/* 0x1d */
-		SQL_INTEGER,	/* 0x1e */
-		SQL_NULL,	/* 0x1f */
-	};
-	return aType[pVal->flags & MEM_PURE_TYPE_MASK];
+enum mp_type
+sql_value_mp_type(sql_value *pVal)
+{
+	switch (pVal->flags & MEM_PURE_TYPE_MASK) {
+	case MEM_Int: return MP_INT;
+	case MEM_Real: return MP_DOUBLE;
+	case MEM_Str: return MP_STR;
+	case MEM_Blob: return MP_BIN;
+	case MEM_Bool: return MP_BOOL;
+	case MEM_Null: return MP_NIL;
+	default: unreachable();
+	}
 }
 
 /* Make a copy of an sql_value object
@@ -1036,12 +1010,12 @@ sql_column_value(sql_stmt * pStmt, int i)
 	return (sql_value *) pOut;
 }
 
-int
+enum mp_type
 sql_column_type(sql_stmt * pStmt, int i)
 {
-	int iType = sql_value_type(columnMem(pStmt, i));
+	enum mp_type type = sql_value_mp_type(columnMem(pStmt, i));
 	columnMallocFailure(pStmt);
-	return iType;
+	return type;
 }
 
 enum sql_subtype
@@ -1414,16 +1388,16 @@ int
 sql_bind_value(sql_stmt * pStmt, int i, const sql_value * pValue)
 {
 	int rc;
-	switch (sql_value_type((sql_value *) pValue)) {
-	case SQL_INTEGER:{
+	switch (sql_value_mp_type((sql_value *) pValue)) {
+	case MP_INT:{
 			rc = sql_bind_int64(pStmt, i, pValue->u.i);
 			break;
 		}
-	case SQL_FLOAT:{
+	case MP_DOUBLE:{
 			rc = sql_bind_double(pStmt, i, pValue->u.r);
 			break;
 		}
-	case SQL_BLOB:{
+	case MP_BIN:{
 			if (pValue->flags & MEM_Zero) {
 				rc = sql_bind_zeroblob(pStmt, i,
 							   pValue->u.nZero);
@@ -1434,7 +1408,7 @@ sql_bind_value(sql_stmt * pStmt, int i, const sql_value * pValue)
 			}
 			break;
 		}
-	case SQL_TEXT:{
+	case MP_STR:{
 			rc = bindText(pStmt, i, pValue->z, pValue->n,
 				      SQL_TRANSIENT);
 			break;
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index 1fb8fd29f..a80c7a0ab 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -290,7 +290,7 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
 		pVal =
 		    sqlVdbeGetBoundValue(pReprepare, iCol,
 					     FIELD_TYPE_SCALAR);
-		if (pVal && sql_value_type(pVal) == SQL_TEXT) {
+		if (pVal && sql_value_mp_type(pVal) == MP_STR) {
 			z = (char *)sql_value_text(pVal);
 		}
 		sqlVdbeSetVarmask(pParse->pVdbe, iCol);
-- 
2.15.1

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

* [tarantool-patches] [PATCH 4/9] sql: introduce type boolean
  2019-04-14 15:03 [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL Nikita Pettik
                   ` (2 preceding siblings ...)
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 3/9] sql: use msgpack types instead of custom ones Nikita Pettik
@ 2019-04-14 15:04 ` Nikita Pettik
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 5/9] sql: improve type determination for column meta Nikita Pettik
                   ` (6 subsequent siblings)
  10 siblings, 1 reply; 42+ messages in thread
From: Nikita Pettik @ 2019-04-14 15:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

This patch introduces basic facilities to operate on boolean type:
boolean literals "true" and "false" where true > false; alias to null -
unknown; column type "BOOLEAN" and shortcut "BOOL"; opportunity to
insert and select boolean values from table; OR and AND predicates
accept boolean arguments; CAST operation involving boolean type;
comparison between boolean values (including VDBE sorter routines).

Part of #3648
---
 extra/mkkeywordhash.c        |   5 +
 src/box/execute.c            |   9 +
 src/box/lua/lua_sql.c        |   3 +
 src/box/sql/build.c          |  11 +-
 src/box/sql/expr.c           |  14 +
 src/box/sql/func.c           |  15 ++
 src/box/sql/parse.y          |  15 ++
 src/box/sql/sqlInt.h         |   6 +
 src/box/sql/vdbe.c           |  34 ++-
 src/box/sql/vdbeInt.h        |   6 +-
 src/box/sql/vdbeapi.c        |  16 ++
 src/box/sql/vdbeaux.c        |  31 ++-
 src/box/sql/vdbemem.c        |  73 +++++-
 test/sql-tap/whereG.test.lua |   6 +-
 test/sql/types.result        | 601 ++++++++++++++++++++++++++++++++++++++++++-
 test/sql/types.test.lua      | 132 ++++++++++
 16 files changed, 952 insertions(+), 25 deletions(-)

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index be7bd5545..8466cc8ee 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -109,6 +109,8 @@ static Keyword aKeywordTable[] = {
   { "BEFORE",                 "TK_BEFORE",      TRIGGER,          false },
   { "BEGIN",                  "TK_BEGIN",       TRIGGER,          true  },
   { "BETWEEN",                "TK_BETWEEN",     ALWAYS,           true  },
+  { "BOOL",                   "TK_BOOLEAN",     ALWAYS,           true  },
+  { "BOOLEAN",                "TK_BOOLEAN",     ALWAYS,           true  },
   { "BY",                     "TK_BY",          ALWAYS,           true  },
   { "CASCADE",                "TK_CASCADE",     FKEY,             false },
   { "CASE",                   "TK_CASE",        ALWAYS,           true  },
@@ -140,6 +142,7 @@ static Keyword aKeywordTable[] = {
   { "EXISTS",                 "TK_EXISTS",      ALWAYS,           true  },
   { "EXPLAIN",                "TK_EXPLAIN",     EXPLAIN,          true  },
   { "FAIL",                   "TK_FAIL",        CONFLICT|TRIGGER, false },
+  { "FALSE",                  "TK_FALSE",       ALWAYS,           true  },
   { "FOR",                    "TK_FOR",         TRIGGER,          true  },
   { "FOREIGN",                "TK_FOREIGN",     FKEY,             true  },
   { "FROM",                   "TK_FROM",        ALWAYS,           true  },
@@ -202,8 +205,10 @@ static Keyword aKeywordTable[] = {
   { "TO",                     "TK_TO",          ALWAYS,           true  },
   { "TRANSACTION",            "TK_TRANSACTION", ALWAYS,           true  },
   { "TRIGGER",                "TK_TRIGGER",     TRIGGER,          true  },
+  { "TRUE",                   "TK_TRUE",        ALWAYS,           true  },
   { "UNION",                  "TK_UNION",       COMPOUND,         true  },
   { "UNIQUE",                 "TK_UNIQUE",      ALWAYS,           true  },
+  { "UNKNOWN",                "TK_NULL",        ALWAYS,           true  },
   { "UPDATE",                 "TK_UPDATE",      ALWAYS,           true  },
   { "USING",                  "TK_USING",       ALWAYS,           true  },
   { "VALUES",                 "TK_VALUES",      ALWAYS,           true  },
diff --git a/src/box/execute.c b/src/box/execute.c
index 2da2bc88b..180519196 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -189,6 +189,15 @@ sql_column_to_messagepack(struct sql_stmt *stmt, int i,
 		}
 		break;
 	}
+	case MP_BOOL: {
+		bool b = sql_column_boolean(stmt, i);
+		size = mp_sizeof_bool(b);
+		char *pos = (char *) region_alloc(region, size);
+		if (pos == NULL)
+			goto oom;
+		mp_encode_bool(pos, b);
+		break;
+	}
 	case MP_NIL: {
 		size = mp_sizeof_nil();
 		char *pos = (char *) region_alloc(region, size);
diff --git a/src/box/lua/lua_sql.c b/src/box/lua/lua_sql.c
index 239ed1b2f..b4b2abab8 100644
--- a/src/box/lua/lua_sql.c
+++ b/src/box/lua/lua_sql.c
@@ -73,6 +73,9 @@ lua_sql_call(sql_context *pCtx, int nVal, sql_value **apVal) {
 		case MP_NIL:
 			lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_nil_ref);
 			break;
+		case MP_BOOL:
+			lua_pushboolean(L, sql_value_boolean(param));
+			break;
 		default:
 			sql_result_error(pCtx, "Unsupported type passed "
 					     "to Lua", -1);
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 7724e9415..c6fbb1af6 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -906,7 +906,6 @@ emitNewSysSequenceRecord(Parse *pParse, int reg_seq_id, const char *seq_name)
 
 	const long long int min_usigned_long_long = 0;
 	const long long int max_usigned_long_long = LLONG_MAX;
-	const bool const_false = false;
 
 	/* 1. New sequence id  */
 	sqlVdbeAddOp2(v, OP_SCopy, reg_seq_id, first_col + 1);
@@ -932,8 +931,7 @@ emitNewSysSequenceRecord(Parse *pParse, int reg_seq_id, const char *seq_name)
 	sqlVdbeAddOp2(v, OP_Integer, 0, first_col + 8);
 
 	/* 9. Cycle  */
-	sqlVdbeAddOp2(v, OP_Bool, 0, first_col + 9);
-	sqlVdbeChangeP4(v, -1, (char*)&const_false, P4_BOOL);
+	sqlVdbeAddOp2(v, OP_Bool, false, first_col + 9);
 
 	sqlVdbeAddOp3(v, OP_MakeRecord, first_col + 1, 9, first_col);
 
@@ -947,7 +945,6 @@ int
 emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id)
 {
 	Vdbe *v = sqlGetVdbe(pParse);
-	const bool const_true = true;
 	int first_col = pParse->nMem + 1;
 	pParse->nMem += 4; /* 3 fields + new record pointer  */
 
@@ -958,8 +955,7 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id
 	sqlVdbeAddOp2(v, OP_IntCopy, reg_seq_id, first_col + 2);
 
 	/* 3. True, which is 1 in SQL  */
-	sqlVdbeAddOp2(v, OP_Bool, 0, first_col + 3);
-	sqlVdbeChangeP4(v, -1, (char*)&const_true, P4_BOOL);
+	sqlVdbeAddOp2(v, OP_Bool, true, first_col + 3);
 
 	sqlVdbeAddOp3(v, OP_MakeRecord, first_col + 1, 3, first_col);
 
@@ -1023,8 +1019,7 @@ vdbe_emit_fk_constraint_create(struct Parse *parse_context,
 					      ER_CONSTRAINT_EXISTS, error_msg,
 					      false, OP_NoConflict) != 0)
 		return;
-	sqlVdbeAddOp2(vdbe, OP_Bool, 0, constr_tuple_reg + 3);
-	sqlVdbeChangeP4(vdbe, -1, (char*)&fk->is_deferred, P4_BOOL);
+	sqlVdbeAddOp2(vdbe, OP_Bool, fk->is_deferred, constr_tuple_reg + 3);
 	sqlVdbeAddOp4(vdbe, OP_String8, 0, constr_tuple_reg + 4, 0,
 			  fk_constraint_match_strs[fk->match], P4_STATIC);
 	sqlVdbeAddOp4(vdbe, OP_String8, 0, constr_tuple_reg + 5, 0,
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 4b98bd175..6b38e8e66 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -3360,6 +3360,15 @@ expr_code_int(struct Parse *parse, struct Expr *expr, bool is_neg,
 	}
 }
 
+static void
+vdbe_emit_bool(struct Vdbe *v, const struct Expr *expr, int mem)
+{
+	const char *z = expr->u.zToken;
+	assert(z != NULL);
+	bool val = strncasecmp(z, "TRUE", 4) == 0;
+	sqlVdbeAddOp2(v, OP_Bool, val, mem);
+}
+
 /*
  * Erase column-cache entry number i
  */
@@ -3764,6 +3773,11 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			expr_code_int(pParse, pExpr, false, target);
 			return target;
 		}
+	case TK_TRUE:
+	case TK_FALSE: {
+			vdbe_emit_bool(pParse->pVdbe, pExpr, target);
+			return target;
+		}
 #ifndef SQL_OMIT_FLOATING_POINT
 	case TK_FLOAT:{
 			pExpr->type = FIELD_TYPE_INTEGER;
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 3cdb119c8..860cd8920 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -122,6 +122,9 @@ typeofFunc(sql_context * context, int NotUsed, sql_value ** argv)
 	case MP_BIN:
 		z = "blob";
 		break;
+	case MP_BOOL:
+		z = "boolean";
+		break;
 	default:
 		z = "null";
 		break;
@@ -197,6 +200,13 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 			sql_result_null(context);
 			break;
 		}
+	case MP_BOOL: {
+		diag_set(ClientError, ER_INCONSISTENT_TYPES, "number",
+			 "boolean");
+		context->isError = SQL_TARANTOOL_ERROR;
+		context->fErrorOrAux = 1;
+		return;
+	}
 	default:{
 			/* Because sql_value_double() returns 0.0 if the argument is not
 			 * something that can be converted into a number, we have:
@@ -1091,6 +1101,11 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 			}
 			break;
 		}
+	case MP_BOOL: {
+		sql_result_text(context, argv[0]->u.b ? "true" : "false", -1,
+				SQL_TRANSIENT);
+		break;
+	}
 	default:{
 			assert(sql_value_mp_type(argv[0]) == MP_NIL);
 			sql_result_text(context, "NULL", 4, SQL_STATIC);
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index ed5c05436..f64a84948 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -918,6 +918,11 @@ idlist(A) ::= nm(Y). {
       case TK_FLOAT:
         p->type = FIELD_TYPE_NUMBER;
         break;
+      case TK_TRUE:
+      case TK_FALSE:
+      case TK_UNKNOWN:
+        p->type = FIELD_TYPE_BOOLEAN;
+        break;
       case TK_VARIABLE:
         /*
          * For variables we set BOOLEAN type since
@@ -979,6 +984,10 @@ expr(A) ::= nm(X) DOT nm(Y). {
 }
 term(A) ::= FLOAT|BLOB(X). {spanExpr(&A,pParse,@X,X);/*A-overwrites-X*/}
 term(A) ::= STRING(X).     {spanExpr(&A,pParse,@X,X);/*A-overwrites-X*/}
+term(A) ::= FALSE(X) . {spanExpr(&A,pParse,@X,X);/*A-overwrites-X*/}
+term(A) ::= TRUE(X) . {spanExpr(&A,pParse,@X,X);/*A-overwrites-X*/}
+term(A) ::= UNKNOWN(X) . {spanExpr(&A,pParse,@X,X);/*A-overwrites-X*/}
+
 term(A) ::= INTEGER(X). {
   A.pExpr = sql_expr_new_dequoted(pParse->db, TK_INTEGER, &X);
   if (A.pExpr == NULL) {
@@ -1435,6 +1444,8 @@ cmd ::= PRAGMA .                            {
 
 nmnum(A) ::= plus_num(A).
 nmnum(A) ::= STRING(A).
+nmnum(A) ::= TRUE(A).
+nmnum(A) ::= FALSE(A).
 nmnum(A) ::= nm(A).
 nmnum(A) ::= ON(A).
 nmnum(A) ::= DELETE(A).
@@ -1667,6 +1678,10 @@ wqlist(A) ::= wqlist(A) COMMA nm(X) eidlist_opt(Y) AS LP select(Z) RP. {
 %type typedef {struct type_def}
 typedef(A) ::= TEXT . { A.type = FIELD_TYPE_STRING; }
 typedef(A) ::= SCALAR . { A.type = FIELD_TYPE_SCALAR; }
+/** BOOL | BOOLEAN is not used due to possible bug in Lemon. */
+typedef(A) ::= BOOL . { A.type = FIELD_TYPE_BOOLEAN; }
+typedef(A) ::= BOOLEAN . { A.type = FIELD_TYPE_BOOLEAN; }
+
 /**
  * Time-like types are temporary disabled, until they are
  * implemented as a native Tarantool types (gh-3694).
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 9134f767d..4f62c2782 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -437,6 +437,9 @@ sql_value_bytes(sql_value *);
 double
 sql_value_double(sql_value *);
 
+bool
+sql_value_boolean(sql_value *val);
+
 int
 sql_value_int(sql_value *);
 
@@ -573,6 +576,9 @@ sql_column_double(sql_stmt *, int iCol);
 int
 sql_column_int(sql_stmt *, int iCol);
 
+bool
+sql_column_boolean(sql_stmt *stmt, int column);
+
 sql_int64
 sql_column_int64(sql_stmt *, int iCol);
 
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index c689aaf1d..10794b18e 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -333,6 +333,10 @@ mem_apply_type(struct Mem *record, enum field_type type)
 			return 0;
 		}
 		return sqlVdbeMemIntegerify(record, false);
+	case FIELD_TYPE_BOOLEAN:
+		if ((record->flags & MEM_Bool) == MEM_Bool)
+			return 0;
+		return -1;
 	case FIELD_TYPE_NUMBER:
 		if ((record->flags & (MEM_Real | MEM_Int)) != 0)
 			return 0;
@@ -504,6 +508,8 @@ memTracePrint(Mem *p)
 	} else if (p->flags & MEM_Real) {
 		printf(" r:%g", p->u.r);
 #endif
+	} else if (p->flags & MEM_Bool) {
+		printf(" bool:%s", p->u.b ? "true" : "false");
 	} else {
 		char zBuf[200];
 		sqlVdbeMemPrettyPrint(p, zBuf);
@@ -1091,9 +1097,9 @@ case OP_Integer: {         /* out2 */
  */
 case OP_Bool: {         /* out2 */
 	pOut = out2Prerelease(p, pOp);
-	assert(pOp->p4type == P4_BOOL);
+	assert(pOp->p1 == 1 || pOp->p1 == 0);
 	pOut->flags = MEM_Bool;
-	pOut->u.b = pOp->p4.p;
+	pOut->u.b = pOp->p1;
 	break;
 }
 
@@ -2161,6 +2167,24 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
 			}
 			break;
 		}
+	} else if ((flags1 | flags3) & MEM_Bool) {
+		/*
+		 * If one of values is of type BOOLEAN, then the
+		 * second one must be BOOLEAN as well. Otherwise
+		 * an error is raised.
+		 */
+		bool is_bool_type_arg1 = flags1 & MEM_Bool;
+		bool is_bool_type_arg3 = flags3 & MEM_Bool;
+		if (! is_bool_type_arg1 || ! is_bool_type_arg3) {
+			char *inconsistent_type = ! is_bool_type_arg1 ?
+						  mem_type_to_str(pIn1) :
+						  mem_type_to_str(pIn3);
+			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+				 inconsistent_type, "boolean");
+			rc = SQL_TARANTOOL_ERROR;
+			goto abort_due_to_error;
+		}
+		res = sqlMemCompare(pIn3, pIn1, NULL);
 	} else {
 		enum field_type type = pOp->p5 & FIELD_TYPE_MASK;
 		if (sql_type_is_numeric(type)) {
@@ -2414,6 +2438,8 @@ case OP_Or: {             /* same as TK_OR, in1, in2, out3 */
 	pIn1 = &aMem[pOp->p1];
 	if (pIn1->flags & MEM_Null) {
 		v1 = 2;
+	} else if ((pIn1->flags & MEM_Bool) != 0) {
+		v1 = pIn1->u.b;
 	} else {
 		int64_t i;
 		if (sqlVdbeIntValue(pIn1, &i) != 0) {
@@ -2427,6 +2453,8 @@ case OP_Or: {             /* same as TK_OR, in1, in2, out3 */
 	pIn2 = &aMem[pOp->p2];
 	if (pIn2->flags & MEM_Null) {
 		v2 = 2;
+	} else if ((pIn2->flags & MEM_Bool) != 0) {
+		v2 = pIn2->u.b;
 	} else {
 		int64_t i;
 		if (sqlVdbeIntValue(pIn2, &i) != 0) {
@@ -2540,6 +2568,8 @@ case OP_IfNot: {            /* jump, in1 */
 	pIn1 = &aMem[pOp->p1];
 	if (pIn1->flags & MEM_Null) {
 		c = pOp->p3;
+	} else if ((pIn1->flags & MEM_Bool) != 0) {
+		c = pOp->opcode==OP_IfNot ? ! pIn1->u.b : pIn1->u.b;
 	} else {
 		double v;
 		if (sqlVdbeRealValue(pIn1, &v) != 0) {
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index ec9123a66..5251e76ab 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -259,7 +259,7 @@ struct Mem {
  * auxiliary flags.
  */
 enum {
-	MEM_PURE_TYPE_MASK = 0x1f
+	MEM_PURE_TYPE_MASK = 0x3f
 };
 
 /**
@@ -508,6 +508,10 @@ int sqlVdbeMemStringify(Mem *, u8);
 int sqlVdbeIntValue(Mem *, int64_t *);
 int sqlVdbeMemIntegerify(Mem *, bool is_forced);
 int sqlVdbeRealValue(Mem *, double *);
+
+int
+vdbe_value_boolean(Mem *mem, bool *b);
+
 int mem_apply_integer_type(Mem *);
 int sqlVdbeMemRealify(Mem *);
 int sqlVdbeMemNumerify(Mem *);
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 6e867ca84..e1302afc0 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -211,6 +211,14 @@ sql_value_double(sql_value * pVal)
 	return v;
 }
 
+bool
+sql_value_boolean(sql_value *val)
+{
+	bool b;
+	vdbe_value_boolean((struct Mem *) val, &b);
+	return b;
+}
+
 int
 sql_value_int(sql_value * pVal)
 {
@@ -982,6 +990,14 @@ sql_column_int(sql_stmt * pStmt, int i)
 	return val;
 }
 
+bool
+sql_column_boolean(sql_stmt *stmt, int i)
+{
+	bool val = sql_value_boolean(columnMem(stmt, i));
+	columnMallocFailure(stmt);
+	return val;
+}
+
 sql_int64
 sql_column_int64(sql_stmt * pStmt, int i)
 {
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 0cc3c1487..0f56028e5 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -3383,6 +3383,17 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
 		return (f2 & MEM_Null) - (f1 & MEM_Null);
 	}
 
+	if (combined_flags & MEM_Bool) {
+		if ((f1 & f2 & MEM_Bool) != 0) {
+			if (pMem1->u.b == pMem2->u.b)
+				return 0;
+			if (pMem1->u.b)
+				return 1;
+			return -1;
+		}
+		return -1;
+	}
+
 	/* At least one of the two values is a number
 	 */
 	if (combined_flags & (MEM_Int | MEM_Real)) {
@@ -3561,10 +3572,16 @@ sqlVdbeCompareMsgpack(const char **key1,
 			break;
 		}
 	case MP_BOOL:{
-			assert((unsigned char)(*aKey1) == 0xc2
-			       || (unsigned char)*aKey1 == 0xc3);
-			mem1.u.i = (unsigned)(size_t) aKey1++ - 0xc2;
-			goto do_int;
+
+			mem1.u.b = mp_decode_bool(&aKey1);
+			if ((pKey2->flags & MEM_Bool) != 0) {
+				if (mem1.u.b != pKey2->u.b) {
+					rc = mem1.u.b ? 1 : -1;
+				}
+			} else {
+				rc = (pKey2->flags & MEM_Null) != 0 ? +1 : -1;
+			}
+			break;
 		}
 	case MP_UINT:{
 			uint64_t v = mp_decode_uint(&aKey1);
@@ -3716,10 +3733,8 @@ vdbe_decode_msgpack_into_mem(const char *buf, struct Mem *mem, uint32_t *len)
 		break;
 	}
 	case MP_BOOL: {
-		assert((unsigned char)*buf == 0xc2 ||
-		       (unsigned char)*buf == 0xc3);
-		mem->u.i = (unsigned char)*buf - 0xc2;
-		mem->flags = MEM_Int;
+		mem->u.b = mp_decode_bool(&buf);
+		mem->flags = MEM_Bool;
 		break;
 	}
 	case MP_UINT: {
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 15a2f55cb..4fed0eefe 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -289,7 +289,7 @@ sqlVdbeMemStringify(Mem * pMem, u8 bForce)
 		return SQL_OK;
 
 	assert(!(fg & MEM_Zero));
-	assert(fg & (MEM_Int | MEM_Real));
+	assert(fg & (MEM_Int | MEM_Real | MEM_Bool));
 	assert(EIGHT_BYTE_ALIGNMENT(pMem));
 
 	if (sqlVdbeMemClearAndResize(pMem, nByte)) {
@@ -297,6 +297,8 @@ sqlVdbeMemStringify(Mem * pMem, u8 bForce)
 	}
 	if (fg & MEM_Int) {
 		sql_snprintf(nByte, pMem->z, "%lld", pMem->u.i);
+	} else if ((fg & MEM_Bool) != 0) {
+		sql_snprintf(nByte, pMem->z, "%s", pMem->u.b ? "true" : "false");
 	} else {
 		assert(fg & MEM_Real);
 		sql_snprintf(nByte, pMem->z, "%!.15g", pMem->u.r);
@@ -499,6 +501,16 @@ sqlVdbeRealValue(Mem * pMem, double *v)
 	return -1;
 }
 
+int
+vdbe_value_boolean(Mem *mem, bool *b)
+{
+	if (mem->flags  & MEM_Bool) {
+		*b = mem->u.b;
+		return 0;
+	}
+	return -1;
+}
+
 /*
  * The MEM structure is already a MEM_Real.  Try to also make it a
  * MEM_Int if we can.
@@ -594,6 +606,37 @@ sqlVdbeMemNumerify(Mem * pMem)
 	return SQL_OK;
 }
 
+/**
+ * According to ANSI SQL string value can be converted to boolean
+ * type if string consists of literal "true" or "false" and
+ * number of leading spaces.
+ *
+ * For instance, "   tRuE" can be successfully converted to
+ * boolean value true.
+ *
+ * @param str String to be converted to boolean.
+ *            Assumed to be null terminated.
+ * @param result Resulting value of cast.
+ * @retval 0 If string satisfies conditions above.
+ * @retval -1 Otherwise.
+ */
+static int
+str_cast_to_boolean(const char *str, bool *result)
+{
+	assert(str != NULL);
+	for (; *str == ' '; str++);
+	size_t rest_str_len = strlen(str);
+	if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
+		*result = true;
+		return 0;
+	}
+	if (rest_str_len == 5 && strncasecmp(str, "false", 5) == 0) {
+		*result = false;
+		return 0;
+	}
+	return -1;
+}
+
 /*
  * Cast the datatype of the value in pMem according to the type
  * @type.  Casting is different from applying type in that a cast
@@ -618,6 +661,23 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type)
 	switch (type) {
 	case FIELD_TYPE_SCALAR:
 		return 0;
+	case FIELD_TYPE_BOOLEAN:
+		if ((pMem->flags & MEM_Int) != 0) {
+			pMem->u.b = pMem->u.i;
+			MemSetTypeFlag(pMem, MEM_Bool);
+			return 0;
+		}
+		if ((pMem->flags & MEM_Str) != 0) {
+			bool value;
+			if (str_cast_to_boolean(pMem->z, &value) != 0)
+				return -1;
+			MemSetTypeFlag(pMem, MEM_Bool);
+			pMem->u.b = value;
+			return 0;
+		}
+		if ((pMem->flags & MEM_Bool)  != 0)
+			return 0;
+		return -1;
 	case FIELD_TYPE_INTEGER:
 		if ((pMem->flags & MEM_Blob) != 0) {
 			if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i,
@@ -626,12 +686,23 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type)
 			MemSetTypeFlag(pMem, MEM_Int);
 			return 0;
 		}
+		if ((pMem->flags & MEM_Bool) != 0) {
+			pMem->u.i = pMem->u.b;
+			MemSetTypeFlag(pMem, MEM_Int);
+			return 0;
+		}
 		return sqlVdbeMemIntegerify(pMem, true);
 	case FIELD_TYPE_NUMBER:
 		return sqlVdbeMemRealify(pMem);
 	default:
 		assert(type == FIELD_TYPE_STRING);
 		assert(MEM_Str == (MEM_Blob >> 3));
+		if ((pMem->flags & MEM_Bool) != 0) {
+			const char *str_bool = pMem->u.b ? "TRUE" : "FALSE";
+			sqlVdbeMemSetStr(pMem, str_bool, strlen(str_bool), 1,
+					 SQL_TRANSIENT);
+			return 0;
+		}
 		pMem->flags |= (pMem->flags & MEM_Blob) >> 3;
 			sql_value_apply_type(pMem, FIELD_TYPE_STRING);
 		assert(pMem->flags & MEM_Str || pMem->db->mallocFailed);
diff --git a/test/sql-tap/whereG.test.lua b/test/sql-tap/whereG.test.lua
index 510df2e4f..e9a39c5ce 100755
--- a/test/sql-tap/whereG.test.lua
+++ b/test/sql-tap/whereG.test.lua
@@ -329,10 +329,10 @@ test:do_execsql_test(
         CREATE TABLE t1(i int PRIMARY KEY, x INT , y INT , z INT );
         INSERT INTO t1 VALUES (1,1,1,1), (2,2,2,2), (3,3,3,3), (4,4,4,4);
         DROP TABLE IF EXISTS t2;
-        CREATE TABLE t2(i int PRIMARY KEY, bool TEXT);
+        CREATE TABLE t2(i int PRIMARY KEY, b TEXT);
         INSERT INTO t2 VALUES(1,'T'), (2,'F');
-        SELECT count(*) FROM t1 LEFT JOIN t2 ON t1.i=t2.i AND bool='T' union all
-        SELECT count(*) FROM t1 LEFT JOIN t2 ON likely(t1.i=t2.i) AND bool='T';
+        SELECT count(*) FROM t1 LEFT JOIN t2 ON t1.i=t2.i AND b='T' union all
+        SELECT count(*) FROM t1 LEFT JOIN t2 ON likely(t1.i=t2.i) AND b='T';
     ]], {
         -- <6.0>
         4, 4
diff --git a/test/sql/types.result b/test/sql/types.result
index 0a5085658..3aa0169e2 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -142,8 +142,8 @@ box.execute("SELECT * FROM test")
   - name: A
     type: boolean
   rows:
-  - [1, 1]
-  - [2, 0]
+  - [1, true]
+  - [2, false]
 ...
 sp:drop()
 ---
@@ -247,3 +247,600 @@ box.execute("SELECT NULL LIKE s FROM t1;")
 box.space.T1:drop()
 ---
 ...
+-- Test basic capabilities of boolean type.
+--
+box.execute("SELECT true;")
+---
+- metadata:
+  - name: 'true'
+    type: boolean
+  rows:
+  - [true]
+...
+box.execute("SELECT false;")
+---
+- metadata:
+  - name: 'false'
+    type: boolean
+  rows:
+  - [false]
+...
+box.execute("SELECT unknown;")
+---
+- metadata:
+  - name: unknown
+    type: scalar
+  rows:
+  - [null]
+...
+box.execute("SELECT true = false;")
+---
+- metadata:
+  - name: true = false
+    type: integer
+  rows:
+  - [0]
+...
+box.execute("SELECT true = true;")
+---
+- metadata:
+  - name: true = true
+    type: integer
+  rows:
+  - [1]
+...
+box.execute("SELECT true > false;")
+---
+- metadata:
+  - name: true > false
+    type: integer
+  rows:
+  - [1]
+...
+box.execute("SELECT true < false;")
+---
+- metadata:
+  - name: true < false
+    type: integer
+  rows:
+  - [0]
+...
+box.execute("SELECT null = true;")
+---
+- metadata:
+  - name: null = true
+    type: integer
+  rows:
+  - [null]
+...
+box.execute("SELECT unknown = true;")
+---
+- metadata:
+  - name: unknown = true
+    type: integer
+  rows:
+  - [null]
+...
+box.execute("SELECT 1 = true;")
+---
+- error: 'Type mismatch: can not convert INTEGER to boolean'
+...
+box.execute("SELECT 'abc' = true;")
+---
+- error: 'Type mismatch: can not convert TEXT to boolean'
+...
+box.execute("SELECT 1.123 > true;")
+---
+- error: 'Type mismatch: can not convert REAL to boolean'
+...
+box.execute("SELECT true IN (1, 'abc', true)")
+---
+- metadata:
+  - name: true IN (1, 'abc', true)
+    type: integer
+  rows:
+  - [1]
+...
+box.execute("SELECT true IN (1, 'abc', false)")
+---
+- metadata:
+  - name: true IN (1, 'abc', false)
+    type: integer
+  rows:
+  - [0]
+...
+box.execute("SELECT 1 LIMIT true;")
+---
+- error: Only positive integers are allowed in the LIMIT clause
+...
+box.execute("SELECT 1 LIMIT 1 OFFSET true;")
+---
+- error: Only positive integers are allowed in the OFFSET clause
+...
+box.execute("SELECT 'abc' || true;")
+---
+- error: 'Inconsistent types: expected TEXT or BLOB got BOOLEAN'
+...
+-- Boolean can take part in arithmetic operations.
+--
+box.execute("SELECT true + false;")
+---
+- error: 'Type mismatch: can not convert false to numeric'
+...
+box.execute("SELECT true * 1;")
+---
+- error: 'Type mismatch: can not convert true to numeric'
+...
+box.execute("SELECT false / 0;")
+---
+- error: 'Type mismatch: can not convert false to numeric'
+...
+box.execute("SELECT not true;")
+---
+- error: 'Type mismatch: can not convert true to integer'
+...
+box.execute("SELECT ~true;")
+---
+- error: 'Type mismatch: can not convert true to integer'
+...
+box.execute("SELECT -true;")
+---
+- error: 'Type mismatch: can not convert true to numeric'
+...
+box.execute("SELECT true << 1;")
+---
+- error: 'Type mismatch: can not convert true to integer'
+...
+box.execute("SELECT true | 1;")
+---
+- error: 'Type mismatch: can not convert true to integer'
+...
+box.execute("SELECT true and false;")
+---
+- metadata:
+  - name: true and false
+    type: number
+  rows:
+  - [0]
+...
+box.execute("SELECT true or unknown;")
+---
+- metadata:
+  - name: true or unknown
+    type: number
+  rows:
+  - [1]
+...
+box.execute("CREATE TABLE t (id INT PRIMARY KEY, b BOOLEAN);")
+---
+- rowcount: 1
+...
+box.execute("INSERT INTO t VALUES (1, true);")
+---
+- rowcount: 1
+...
+box.execute("INSERT INTO t VALUES (2, false);")
+---
+- rowcount: 1
+...
+box.execute("INSERT INTO t VALUES (3, unknown)")
+---
+- rowcount: 1
+...
+box.execute("SELECT b FROM t;")
+---
+- metadata:
+  - name: B
+    type: boolean
+  rows:
+  - [true]
+  - [false]
+  - [null]
+...
+box.execute("SELECT b FROM t WHERE b = false;")
+---
+- metadata:
+  - name: B
+    type: boolean
+  rows:
+  - [false]
+...
+box.execute("SELECT b FROM t WHERE b IS NULL;")
+---
+- metadata:
+  - name: B
+    type: boolean
+  rows:
+  - [null]
+...
+box.execute("SELECT b FROM t WHERE b IN (false, 1, 'abc')")
+---
+- metadata:
+  - name: B
+    type: boolean
+  rows:
+  - [false]
+...
+box.execute("SELECT b FROM t WHERE b BETWEEN false AND true;")
+---
+- metadata:
+  - name: B
+    type: boolean
+  rows:
+  - [true]
+  - [false]
+...
+box.execute("SELECT b FROM t WHERE b BETWEEN true AND false;")
+---
+- metadata:
+  - name: B
+    type: boolean
+  rows: []
+...
+box.execute("SELECT b FROM t ORDER BY b;")
+---
+- metadata:
+  - name: B
+    type: scalar
+  rows:
+  - [null]
+  - [false]
+  - [true]
+...
+box.execute("SELECT b FROM t ORDER BY +b;")
+---
+- metadata:
+  - name: B
+    type: boolean
+  rows:
+  - [null]
+  - [false]
+  - [true]
+...
+box.execute("SELECT b FROM t ORDER BY b LIMIT 1;")
+---
+- metadata:
+  - name: B
+    type: scalar
+  rows:
+  - [null]
+...
+box.execute("SELECT b FROM t GROUP BY b LIMIT 1;")
+---
+- metadata:
+  - name: B
+    type: scalar
+  rows:
+  - [null]
+...
+box.execute("SELECT b FROM t LIMIT true;")
+---
+- error: Only positive integers are allowed in the LIMIT clause
+...
+-- Most of aggregates don't accept boolean arguments.
+--
+box.execute("SELECT sum(b) FROM t;")
+---
+- error: 'Type mismatch: can not convert true to number'
+...
+box.execute("SELECT avg(b) FROM t;")
+---
+- error: 'Type mismatch: can not convert true to number'
+...
+box.execute("SELECT total(b) FROM t;")
+---
+- error: 'Type mismatch: can not convert true to number'
+...
+box.execute("SELECT min(b) FROM t;")
+---
+- metadata:
+  - name: min(b)
+    type: scalar
+  rows:
+  - [false]
+...
+box.execute("SELECT max(b) FROM t;")
+---
+- metadata:
+  - name: max(b)
+    type: scalar
+  rows:
+  - [true]
+...
+box.execute("SELECT count(b) FROM t;")
+---
+- metadata:
+  - name: count(b)
+    type: integer
+  rows:
+  - [2]
+...
+box.execute("SELECT group_concat(b) FROM t;")
+---
+- metadata:
+  - name: group_concat(b)
+    type: string
+  rows:
+  - ['true,false']
+...
+-- Check other built-in functions.
+--
+box.execute("SELECT lower(b) FROM t;")
+---
+- metadata:
+  - name: lower(b)
+    type: string
+  rows:
+  - ['true']
+  - ['false']
+  - [null]
+...
+box.execute("SELECT upper(b) FROM t;")
+---
+- metadata:
+  - name: upper(b)
+    type: string
+  rows:
+  - ['TRUE']
+  - ['FALSE']
+  - [null]
+...
+box.execute("SELECT abs(b) FROM t;")
+---
+- error: 'Inconsistent types: expected number got boolean'
+...
+box.execute("SELECT typeof(b) FROM t;")
+---
+- metadata:
+  - name: typeof(b)
+    type: string
+  rows:
+  - ['boolean']
+  - ['boolean']
+  - ['null']
+...
+box.execute("SELECT quote(b) FROM t;")
+---
+- metadata:
+  - name: quote(b)
+    type: string
+  rows:
+  - ['true']
+  - ['false']
+  - ['NULL']
+...
+box.execute("SELECT min(b, true) FROM t;")
+---
+- metadata:
+  - name: min(b, true)
+    type: scalar
+  rows:
+  - [true]
+  - [false]
+  - [null]
+...
+box.execute("SELECT quote(b) FROM t;")
+---
+- metadata:
+  - name: quote(b)
+    type: string
+  rows:
+  - ['true']
+  - ['false']
+  - ['NULL']
+...
+-- Test index search using boolean values.
+--
+box.execute("CREATE INDEX ib ON t(b);")
+---
+- rowcount: 1
+...
+box.execute("SELECT b FROM t WHERE b = false;")
+---
+- metadata:
+  - name: B
+    type: boolean
+  rows:
+  - [false]
+...
+box.execute("SELECT b FROM t WHERE b OR unknown ORDER BY b;")
+---
+- metadata:
+  - name: B
+    type: boolean
+  rows:
+  - [true]
+...
+-- Test UPDATE on boolean field.
+--
+box.execute("UPDATE t SET b = true WHERE b = false;")
+---
+- rowcount: 1
+...
+box.execute("SELECT b FROM t;")
+---
+- metadata:
+  - name: B
+    type: boolean
+  rows:
+  - [true]
+  - [true]
+  - [null]
+...
+-- Test constraints functionality.
+--
+box.execute("CREATE TABLE parent (id INT PRIMARY KEY, a BOOLEAN UNIQUE);")
+---
+- rowcount: 1
+...
+box.space.T:truncate()
+---
+...
+box.execute("ALTER TABLE t ADD CONSTRAINT fk1 FOREIGN KEY (b) REFERENCES parent (a);")
+---
+- rowcount: 0
+...
+box.execute("INSERT INTO t VALUES (1, true);")
+---
+- error: 'Failed to execute SQL statement: FOREIGN KEY constraint failed'
+...
+box.execute("INSERT INTO parent VALUES (1, true);")
+---
+- rowcount: 1
+...
+box.execute("INSERT INTO t VALUES (1, true);")
+---
+- rowcount: 1
+...
+box.execute("ALTER TABLE t DROP CONSTRAINT fk1;")
+---
+- rowcount: 0
+...
+box.space.PARENT:drop()
+---
+...
+box.execute("CREATE TABLE t1 (id INT PRIMARY KEY, a BOOLEAN CHECK (a = true));")
+---
+- rowcount: 1
+...
+box.execute("INSERT INTO t1 VALUES (1, false);")
+---
+- error: 'Failed to execute SQL statement: CHECK constraint failed: T1'
+...
+box.execute("INSERT INTO t1 VALUES (1, true);")
+---
+- rowcount: 1
+...
+box.space.T1:drop()
+---
+...
+box.execute("CREATE TABLE t1 (id INT PRIMARY KEY, a BOOLEAN DEFAULT true);")
+---
+- rowcount: 1
+...
+box.execute("INSERT INTO t1 (id) VALUES (1);")
+---
+- rowcount: 1
+...
+box.space.T1:select()
+---
+- - [1, true]
+...
+box.space.T1:drop()
+---
+...
+-- Check that VIEW inherits boolean type.
+--
+box.execute("CREATE VIEW v AS SELECT b FROM t;")
+---
+- rowcount: 1
+...
+box.space.V:format()[1]['type']
+---
+- boolean
+...
+box.space.V:drop()
+---
+...
+-- Test CAST facilities.
+--
+box.execute("SELECT CAST(true AS INTEGER);")
+---
+- metadata:
+  - name: CAST(true AS INTEGER)
+    type: integer
+  rows:
+  - [1]
+...
+box.execute("SELECT CAST(true AS TEXT);")
+---
+- metadata:
+  - name: CAST(true AS TEXT)
+    type: string
+  rows:
+  - ['TRUE']
+...
+box.execute("SELECT CAST(true AS FLOAT);")
+---
+- error: 'Type mismatch: can not convert true to number'
+...
+box.execute("SELECT CAST(true AS SCALAR);")
+---
+- metadata:
+  - name: CAST(true AS SCALAR)
+    type: scalar
+  rows:
+  - [true]
+...
+box.execute("SELECT CAST(1 AS BOOLEAN);")
+---
+- metadata:
+  - name: CAST(1 AS BOOLEAN)
+    type: boolean
+  rows:
+  - [true]
+...
+box.execute("SELECT CAST(1.123 AS BOOLEAN);")
+---
+- error: 'Type mismatch: can not convert 1.123 to boolean'
+...
+box.execute("SELECT CAST('abc' AS BOOLEAN);")
+---
+- error: 'Type mismatch: can not convert abc to boolean'
+...
+box.execute("SELECT CAST('  TrUe' AS BOOLEAN);")
+---
+- metadata:
+  - name: CAST('  TrUe' AS BOOLEAN)
+    type: boolean
+  rows:
+  - [true]
+...
+box.execute("SELECT CAST(X'4D6564766564' AS BOOLEAN);")
+---
+- error: 'Type mismatch: can not convert Medved to boolean'
+...
+-- Make sure that SCALAR can handle boolean values.
+--
+box.execute("CREATE TABLE t1 (id INT PRIMARY KEY, s SCALAR);")
+---
+- rowcount: 1
+...
+box.execute("INSERT INTO t1 SELECT * FROM t;")
+---
+- rowcount: 1
+...
+box.execute("SELECT s FROM t1 WHERE s = true;")
+---
+- metadata:
+  - name: S
+    type: scalar
+  rows:
+  - [true]
+...
+box.execute("INSERT INTO t1 VALUES (3, 'abc'), (4, 12.5);")
+---
+- rowcount: 2
+...
+box.execute("SELECT s FROM t1 WHERE s = true;")
+---
+- error: 'Type mismatch: can not convert TEXT to boolean'
+...
+box.execute("SELECT s FROM t1 WHERE s < true;")
+---
+- error: 'Type mismatch: can not convert TEXT to boolean'
+...
+box.execute("SELECT s FROM t1 WHERE s IN (true, 1, 'abcd')")
+---
+- metadata:
+  - name: S
+    type: scalar
+  rows:
+  - [true]
+...
+box.space.T:drop()
+---
+...
+box.space.T1:drop()
+---
+...
diff --git a/test/sql/types.test.lua b/test/sql/types.test.lua
index dab5872e0..2aed0fe94 100644
--- a/test/sql/types.test.lua
+++ b/test/sql/types.test.lua
@@ -70,3 +70,135 @@ box.execute("SELECT * FROM t1 WHERE s LIKE 'int';")
 box.execute("SELECT * FROM t1 WHERE 'int' LIKE 4;")
 box.execute("SELECT NULL LIKE s FROM t1;")
 box.space.T1:drop()
+
+-- Test basic capabilities of boolean type.
+--
+box.execute("SELECT true;")
+box.execute("SELECT false;")
+box.execute("SELECT unknown;")
+box.execute("SELECT true = false;")
+box.execute("SELECT true = true;")
+box.execute("SELECT true > false;")
+box.execute("SELECT true < false;")
+box.execute("SELECT null = true;")
+box.execute("SELECT unknown = true;")
+box.execute("SELECT 1 = true;")
+box.execute("SELECT 'abc' = true;")
+box.execute("SELECT 1.123 > true;")
+box.execute("SELECT true IN (1, 'abc', true)")
+box.execute("SELECT true IN (1, 'abc', false)")
+box.execute("SELECT 1 LIMIT true;")
+box.execute("SELECT 1 LIMIT 1 OFFSET true;")
+box.execute("SELECT 'abc' || true;")
+
+-- Boolean can take part in arithmetic operations.
+--
+box.execute("SELECT true + false;")
+box.execute("SELECT true * 1;")
+box.execute("SELECT false / 0;")
+box.execute("SELECT not true;")
+box.execute("SELECT ~true;")
+box.execute("SELECT -true;")
+box.execute("SELECT true << 1;")
+box.execute("SELECT true | 1;")
+box.execute("SELECT true and false;")
+box.execute("SELECT true or unknown;")
+
+box.execute("CREATE TABLE t (id INT PRIMARY KEY, b BOOLEAN);")
+box.execute("INSERT INTO t VALUES (1, true);")
+box.execute("INSERT INTO t VALUES (2, false);")
+box.execute("INSERT INTO t VALUES (3, unknown)")
+box.execute("SELECT b FROM t;")
+box.execute("SELECT b FROM t WHERE b = false;")
+box.execute("SELECT b FROM t WHERE b IS NULL;")
+box.execute("SELECT b FROM t WHERE b IN (false, 1, 'abc')")
+box.execute("SELECT b FROM t WHERE b BETWEEN false AND true;")
+box.execute("SELECT b FROM t WHERE b BETWEEN true AND false;")
+box.execute("SELECT b FROM t ORDER BY b;")
+box.execute("SELECT b FROM t ORDER BY +b;")
+box.execute("SELECT b FROM t ORDER BY b LIMIT 1;")
+box.execute("SELECT b FROM t GROUP BY b LIMIT 1;")
+box.execute("SELECT b FROM t LIMIT true;")
+
+-- Most of aggregates don't accept boolean arguments.
+--
+box.execute("SELECT sum(b) FROM t;")
+box.execute("SELECT avg(b) FROM t;")
+box.execute("SELECT total(b) FROM t;")
+box.execute("SELECT min(b) FROM t;")
+box.execute("SELECT max(b) FROM t;")
+box.execute("SELECT count(b) FROM t;")
+box.execute("SELECT group_concat(b) FROM t;")
+
+-- Check other built-in functions.
+--
+box.execute("SELECT lower(b) FROM t;")
+box.execute("SELECT upper(b) FROM t;")
+box.execute("SELECT abs(b) FROM t;")
+box.execute("SELECT typeof(b) FROM t;")
+box.execute("SELECT quote(b) FROM t;")
+box.execute("SELECT min(b, true) FROM t;")
+box.execute("SELECT quote(b) FROM t;")
+
+-- Test index search using boolean values.
+--
+box.execute("CREATE INDEX ib ON t(b);")
+box.execute("SELECT b FROM t WHERE b = false;")
+box.execute("SELECT b FROM t WHERE b OR unknown ORDER BY b;")
+
+-- Test UPDATE on boolean field.
+--
+box.execute("UPDATE t SET b = true WHERE b = false;")
+box.execute("SELECT b FROM t;")
+
+-- Test constraints functionality.
+--
+box.execute("CREATE TABLE parent (id INT PRIMARY KEY, a BOOLEAN UNIQUE);")
+box.space.T:truncate()
+box.execute("ALTER TABLE t ADD CONSTRAINT fk1 FOREIGN KEY (b) REFERENCES parent (a);")
+box.execute("INSERT INTO t VALUES (1, true);")
+box.execute("INSERT INTO parent VALUES (1, true);")
+box.execute("INSERT INTO t VALUES (1, true);")
+box.execute("ALTER TABLE t DROP CONSTRAINT fk1;")
+box.space.PARENT:drop()
+
+box.execute("CREATE TABLE t1 (id INT PRIMARY KEY, a BOOLEAN CHECK (a = true));")
+box.execute("INSERT INTO t1 VALUES (1, false);")
+box.execute("INSERT INTO t1 VALUES (1, true);")
+box.space.T1:drop()
+
+box.execute("CREATE TABLE t1 (id INT PRIMARY KEY, a BOOLEAN DEFAULT true);")
+box.execute("INSERT INTO t1 (id) VALUES (1);")
+box.space.T1:select()
+box.space.T1:drop()
+
+-- Check that VIEW inherits boolean type.
+--
+box.execute("CREATE VIEW v AS SELECT b FROM t;")
+box.space.V:format()[1]['type']
+box.space.V:drop()
+
+-- Test CAST facilities.
+--
+box.execute("SELECT CAST(true AS INTEGER);")
+box.execute("SELECT CAST(true AS TEXT);")
+box.execute("SELECT CAST(true AS FLOAT);")
+box.execute("SELECT CAST(true AS SCALAR);")
+box.execute("SELECT CAST(1 AS BOOLEAN);")
+box.execute("SELECT CAST(1.123 AS BOOLEAN);")
+box.execute("SELECT CAST('abc' AS BOOLEAN);")
+box.execute("SELECT CAST('  TrUe' AS BOOLEAN);")
+box.execute("SELECT CAST(X'4D6564766564' AS BOOLEAN);")
+
+-- Make sure that SCALAR can handle boolean values.
+--
+box.execute("CREATE TABLE t1 (id INT PRIMARY KEY, s SCALAR);")
+box.execute("INSERT INTO t1 SELECT * FROM t;")
+box.execute("SELECT s FROM t1 WHERE s = true;")
+box.execute("INSERT INTO t1 VALUES (3, 'abc'), (4, 12.5);")
+box.execute("SELECT s FROM t1 WHERE s = true;")
+box.execute("SELECT s FROM t1 WHERE s < true;")
+box.execute("SELECT s FROM t1 WHERE s IN (true, 1, 'abcd')")
+
+box.space.T:drop()
+box.space.T1:drop()
-- 
2.15.1

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

* [tarantool-patches] [PATCH 5/9] sql: improve type determination for column meta
  2019-04-14 15:03 [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL Nikita Pettik
                   ` (3 preceding siblings ...)
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 4/9] sql: introduce type boolean Nikita Pettik
@ 2019-04-14 15:04 ` Nikita Pettik
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 6/9] sql: make comparison predicate return boolean Nikita Pettik
                   ` (5 subsequent siblings)
  10 siblings, 1 reply; 42+ messages in thread
From: Nikita Pettik @ 2019-04-14 15:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

In most cases we don't assign and store type of node of expression AST
(except for constant literals).  To determine type of node  we use
sql_expr_type() function, which implements logic of quite simple
recursive tree traversal. Before this patch we set type of node after
code generation in sqlExprCodeTarget() without any traversal. This
approach is way worse even then sql_expr_type(). So, to improve accuracy
of type determination, let's always call that method and remove type
assignment in sqlExprCodeTarget().
---
 src/box/sql/expr.c                         | 19 ++++---------------
 src/box/sql/select.c                       |  2 +-
 test/sql/check-clear-ephemeral.result      |  2 +-
 test/sql/gh-2347-max-int-literals.result   |  2 +-
 test/sql/gh-3199-no-mem-leaks.result       | 18 +++++++++---------
 test/sql/gh-3888-values-blob-assert.result |  2 +-
 test/sql/persistency.result                |  8 ++++----
 test/sql/transition.result                 |  4 ++--
 test/sql/types.result                      | 10 +++++-----
 9 files changed, 28 insertions(+), 39 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 6b38e8e66..09ebdba66 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -105,6 +105,10 @@ sql_expr_type(struct Expr *pExpr)
 	case TK_NOT:
 	case TK_AND:
 	case TK_OR:
+	case TK_ISNULL:
+	case TK_NOTNULL:
+	case TK_BETWEEN:
+	case TK_IN:
 		/*
 		 * FIXME: should be changed to BOOL type
 		 * when it is implemented. Now simply
@@ -3769,7 +3773,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 							pExpr->op2);
 		}
 	case TK_INTEGER:{
-			pExpr->type = FIELD_TYPE_INTEGER;
 			expr_code_int(pParse, pExpr, false, target);
 			return target;
 		}
@@ -3780,14 +3783,12 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 		}
 #ifndef SQL_OMIT_FLOATING_POINT
 	case TK_FLOAT:{
-			pExpr->type = FIELD_TYPE_INTEGER;
 			assert(!ExprHasProperty(pExpr, EP_IntValue));
 			codeReal(v, pExpr->u.zToken, 0, target);
 			return target;
 		}
 #endif
 	case TK_STRING:{
-			pExpr->type = FIELD_TYPE_STRING;
 			assert(!ExprHasProperty(pExpr, EP_IntValue));
 			sqlVdbeLoadString(v, target, pExpr->u.zToken);
 			return target;
@@ -3805,7 +3806,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			assert(pExpr->u.zToken[0] == 'x'
 			       || pExpr->u.zToken[0] == 'X');
 			assert(pExpr->u.zToken[1] == '\'');
-			pExpr->type = FIELD_TYPE_SCALAR;
 			z = &pExpr->u.zToken[2];
 			n = sqlStrlen30(z) - 1;
 			assert(z[n] == '\'');
@@ -3887,7 +3887,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				testcase(regFree1 == 0);
 				testcase(regFree2 == 0);
 			}
-			pExpr->type = FIELD_TYPE_INTEGER;
 			break;
 		}
 	case TK_AND:
@@ -3931,10 +3930,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			sqlVdbeAddOp3(v, op, r2, r1, target);
 			testcase(regFree1 == 0);
 			testcase(regFree2 == 0);
-			if (op != TK_CONCAT)
-				pExpr->type = FIELD_TYPE_NUMBER;
-			else
-				pExpr->type = FIELD_TYPE_STRING;
 			break;
 		}
 	case TK_UMINUS:{
@@ -3966,7 +3961,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 		}
 	case TK_BITNOT:
 	case TK_NOT:{
-			pExpr->type = FIELD_TYPE_INTEGER;
 			assert(TK_BITNOT == OP_BitNot);
 			testcase(op == TK_BITNOT);
 			assert(TK_NOT == OP_Not);
@@ -3980,7 +3974,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 	case TK_ISNULL:
 	case TK_NOTNULL:{
 			int addr;
-			pExpr->type = FIELD_TYPE_INTEGER;
 			assert(TK_ISNULL == OP_IsNull);
 			testcase(op == TK_ISNULL);
 			assert(TK_NOTNULL == OP_NotNull);
@@ -4225,7 +4218,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 	case TK_IN:{
 			int destIfFalse = sqlVdbeMakeLabel(v);
 			int destIfNull = sqlVdbeMakeLabel(v);
-			pExpr->type = FIELD_TYPE_INTEGER;
 			sqlVdbeAddOp2(v, OP_Null, 0, target);
 			sqlExprCodeIN(pParse, pExpr, destIfFalse,
 					  destIfNull);
@@ -4249,18 +4241,15 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 		 * Z is stored in pExpr->pList->a[1].pExpr.
 		 */
 	case TK_BETWEEN:{
-			pExpr->type = FIELD_TYPE_INTEGER;
 			exprCodeBetween(pParse, pExpr, target, 0, 0);
 			return target;
 		}
 	case TK_SPAN:
 	case TK_COLLATE:{
-			pExpr->type = FIELD_TYPE_STRING;
 			return sqlExprCodeTarget(pParse, pExpr->pLeft,
 						     target);
 		}
 	case TK_UPLUS:{
-			pExpr->type = FIELD_TYPE_NUMBER;
 			return sqlExprCodeTarget(pParse, pExpr->pLeft,
 						     target);
 		}
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index b1ec8c758..59f22140c 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1760,7 +1760,7 @@ generateColumnNames(Parse * pParse,	/* Parser context */
 		if (p->op == TK_VARIABLE)
 			var_pos[var_count++] = i;
 		sqlVdbeSetColName(v, i, COLNAME_DECLTYPE,
-				  field_type_strs[p->type], SQL_TRANSIENT);
+				  field_type_strs[sql_expr_type(p)], SQL_TRANSIENT);
 		if (pEList->a[i].zName) {
 			char *zName = pEList->a[i].zName;
 			sqlVdbeSetColName(v, i, COLNAME_NAME, zName,
diff --git a/test/sql/check-clear-ephemeral.result b/test/sql/check-clear-ephemeral.result
index 8fbea982c..84dc7af3d 100644
--- a/test/sql/check-clear-ephemeral.result
+++ b/test/sql/check-clear-ephemeral.result
@@ -26,7 +26,7 @@ box.execute("SELECT a FROM t1 ORDER BY b, a LIMIT 10 OFFSET 20;");
 ---
 - metadata:
   - name: A
-    type: scalar
+    type: integer
   rows:
   - [840]
   - [880]
diff --git a/test/sql/gh-2347-max-int-literals.result b/test/sql/gh-2347-max-int-literals.result
index 35bedd0c9..2c2773e8c 100644
--- a/test/sql/gh-2347-max-int-literals.result
+++ b/test/sql/gh-2347-max-int-literals.result
@@ -23,7 +23,7 @@ box.execute("select (-9223372036854775808)")
 ---
 - metadata:
   - name: (-9223372036854775808)
-    type: number
+    type: integer
   rows:
   - [-9223372036854775808]
 ...
diff --git a/test/sql/gh-3199-no-mem-leaks.result b/test/sql/gh-3199-no-mem-leaks.result
index a873c5019..49ed9775d 100644
--- a/test/sql/gh-3199-no-mem-leaks.result
+++ b/test/sql/gh-3199-no-mem-leaks.result
@@ -29,7 +29,7 @@ box.execute('SELECT x, y, x + y FROM test ORDER BY y')
   - name: X
     type: integer
   - name: Y
-    type: scalar
+    type: integer
   - name: x + y
     type: number
   rows:
@@ -46,7 +46,7 @@ box.execute('SELECT x, y, x + y FROM test ORDER BY y')
   - name: X
     type: integer
   - name: Y
-    type: scalar
+    type: integer
   - name: x + y
     type: number
   rows:
@@ -59,7 +59,7 @@ box.execute('SELECT x, y, x + y FROM test ORDER BY y')
   - name: X
     type: integer
   - name: Y
-    type: scalar
+    type: integer
   - name: x + y
     type: number
   rows:
@@ -72,7 +72,7 @@ box.execute('SELECT x, y, x + y FROM test ORDER BY y')
   - name: X
     type: integer
   - name: Y
-    type: scalar
+    type: integer
   - name: x + y
     type: number
   rows:
@@ -85,7 +85,7 @@ box.execute('SELECT x, y, x + y FROM test ORDER BY y')
   - name: X
     type: integer
   - name: Y
-    type: scalar
+    type: integer
   - name: x + y
     type: number
   rows:
@@ -112,7 +112,7 @@ box.execute('SELECT a, id + 2, b FROM test2 WHERE b < id * 2 ORDER BY a ')
 ---
 - metadata:
   - name: A
-    type: scalar
+    type: string
   - name: id + 2
     type: number
   - name: B
@@ -135,7 +135,7 @@ box.execute('SELECT a, id + 2 * b, a FROM test2 WHERE b < id * 2 ORDER BY a ')
   - name: id + 2 * b
     type: number
   - name: A
-    type: scalar
+    type: string
   rows:
   - ['abc', 3, 'abc']
   - ['hello', 6, 'hello']
@@ -150,7 +150,7 @@ box.execute('SELECT a, id + 2 * b, a FROM test2 WHERE b < id * 2 ORDER BY a ')
   - name: id + 2 * b
     type: number
   - name: A
-    type: scalar
+    type: string
   rows:
   - ['abc', 3, 'abc']
   - ['hello', 6, 'hello']
@@ -165,7 +165,7 @@ box.execute('SELECT a, id + 2 * b, a FROM test2 WHERE b < id * 2 ORDER BY a ')
   - name: id + 2 * b
     type: number
   - name: A
-    type: scalar
+    type: string
   rows:
   - ['abc', 3, 'abc']
   - ['hello', 6, 'hello']
diff --git a/test/sql/gh-3888-values-blob-assert.result b/test/sql/gh-3888-values-blob-assert.result
index 5275b0fc7..0d4a27c1f 100644
--- a/test/sql/gh-3888-values-blob-assert.result
+++ b/test/sql/gh-3888-values-blob-assert.result
@@ -64,7 +64,7 @@ box.execute('SELECT 3.14')
 ---
 - metadata:
   - name: '3.14'
-    type: integer
+    type: number
   rows:
   - [3.14]
 ...
diff --git a/test/sql/persistency.result b/test/sql/persistency.result
index b4217f808..cd5dcae1f 100644
--- a/test/sql/persistency.result
+++ b/test/sql/persistency.result
@@ -228,7 +228,7 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar ORDER BY bar")
 ---
 - metadata:
   - name: BAR
-    type: scalar
+    type: string
   - name: FOO
     type: integer
   - name: '42'
@@ -244,7 +244,7 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar ORDER BY bar DESC")
 ---
 - metadata:
   - name: BAR
-    type: scalar
+    type: string
   - name: FOO
     type: integer
   - name: '42'
@@ -341,7 +341,7 @@ box.execute("SELECT a FROM t1 ORDER BY b, a LIMIT 10 OFFSET 20;");
 ---
 - metadata:
   - name: A
-    type: scalar
+    type: integer
   rows:
   - [840]
   - [880]
@@ -446,7 +446,7 @@ box.execute("SELECT a FROM t1 ORDER BY b, a LIMIT 10 OFFSET 20;");
 ---
 - metadata:
   - name: A
-    type: scalar
+    type: integer
   rows:
   - [840]
   - [880]
diff --git a/test/sql/transition.result b/test/sql/transition.result
index adf5d0816..49edf96c6 100644
--- a/test/sql/transition.result
+++ b/test/sql/transition.result
@@ -225,7 +225,7 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar ORDER BY bar")
 ---
 - metadata:
   - name: BAR
-    type: scalar
+    type: string
   - name: FOO
     type: integer
   - name: '42'
@@ -241,7 +241,7 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar ORDER BY bar DESC")
 ---
 - metadata:
   - name: BAR
-    type: scalar
+    type: string
   - name: FOO
     type: integer
   - name: '42'
diff --git a/test/sql/types.result b/test/sql/types.result
index 3aa0169e2..6bccd39ef 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -399,7 +399,7 @@ box.execute("SELECT true and false;")
 ---
 - metadata:
   - name: true and false
-    type: number
+    type: integer
   rows:
   - [0]
 ...
@@ -407,7 +407,7 @@ box.execute("SELECT true or unknown;")
 ---
 - metadata:
   - name: true or unknown
-    type: number
+    type: integer
   rows:
   - [1]
 ...
@@ -481,7 +481,7 @@ box.execute("SELECT b FROM t ORDER BY b;")
 ---
 - metadata:
   - name: B
-    type: scalar
+    type: boolean
   rows:
   - [null]
   - [false]
@@ -501,7 +501,7 @@ box.execute("SELECT b FROM t ORDER BY b LIMIT 1;")
 ---
 - metadata:
   - name: B
-    type: scalar
+    type: boolean
   rows:
   - [null]
 ...
@@ -509,7 +509,7 @@ box.execute("SELECT b FROM t GROUP BY b LIMIT 1;")
 ---
 - metadata:
   - name: B
-    type: scalar
+    type: boolean
   rows:
   - [null]
 ...
-- 
2.15.1

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

* [tarantool-patches] [PATCH 6/9] sql: make comparison predicate return boolean
  2019-04-14 15:03 [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL Nikita Pettik
                   ` (4 preceding siblings ...)
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 5/9] sql: improve type determination for column meta Nikita Pettik
@ 2019-04-14 15:04 ` Nikita Pettik
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 7/9] sql: make predicates accept and " Nikita Pettik
                   ` (4 subsequent siblings)
  10 siblings, 1 reply; 42+ messages in thread
From: Nikita Pettik @ 2019-04-14 15:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

According to ANSI SQL result of comparison predicates must be BOOLEAN.
Before introduction of BOOLEAN type they returned 0 and 1. Now we can
change those values to false and true respectively.

Part of #3723
---
 src/box/sql/expr.c                    |  1 +
 src/box/sql/vdbe.c                    |  4 ++--
 test/sql-tap/aggnested.test.lua       | 10 +++++-----
 test/sql-tap/identifier_case.test.lua | 10 +++++-----
 test/sql-tap/tkt3346.test.lua         |  2 +-
 test/sql/types.result                 | 20 ++++++++++----------
 6 files changed, 24 insertions(+), 23 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 09ebdba66..e4de472a8 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -102,6 +102,7 @@ sql_expr_type(struct Expr *pExpr)
 	case TK_EQ:
 	case TK_LE:
 	case TK_NE:
+		return FIELD_TYPE_BOOLEAN;
 	case TK_NOT:
 	case TK_AND:
 	case TK_OR:
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 10794b18e..43d4262e5 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2273,8 +2273,8 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
 			if ((pOp->opcode==OP_Eq)==res2) break;
 		}
 		memAboutToChange(p, pOut);
-		MemSetTypeFlag(pOut, MEM_Int);
-		pOut->u.i = res2;
+		MemSetTypeFlag(pOut, MEM_Bool);
+		pOut->u.b = res2;
 		REGISTER_TRACE(pOp->p2, pOut);
 	} else {
 		VdbeBranchTaken(res!=0, (pOp->p5 & SQL_NULLEQ)?2:3);
diff --git a/test/sql-tap/aggnested.test.lua b/test/sql-tap/aggnested.test.lua
index 656576b70..67a9ba891 100755
--- a/test/sql-tap/aggnested.test.lua
+++ b/test/sql-tap/aggnested.test.lua
@@ -180,7 +180,7 @@ test:do_execsql_test("aggnested-3.2",
         );
         INSERT INTO t2 VALUES(1);
         SELECT
-         (SELECT sum(value2==xyz) FROM t2)
+         (SELECT sum(xyz) FROM t2 where xyz = value2)
         FROM
          (SELECT value1 as xyz, max(x1) AS pqr
             FROM t1
@@ -207,7 +207,7 @@ test:do_execsql_test("aggnested-3.2-2",
         );
         INSERT INTO t2 VALUES(1);
         SELECT
-         (SELECT sum(value2<>xyz) FROM t2)
+         (SELECT sum(xyz) FROM t2 where xyz <> value2)
         FROM
          (SELECT value1 as xyz, max(x1) AS pqr
             FROM t1
@@ -215,7 +215,7 @@ test:do_execsql_test("aggnested-3.2-2",
     ]],
     {
         -- <aggnested-3.2>
-        0
+        ""
         -- </aggnested-3.2>
     })
 
@@ -227,13 +227,13 @@ test:do_execsql_test("aggnested-3.3",
         INSERT INTO t1 VALUES(4469,2),(4469,1);
         CREATE TABLE t2 (value2 INT PRIMARY KEY);
         INSERT INTO t2 VALUES(1);
-        SELECT (SELECT sum(value2=value1) FROM t2), max(value1)
+        SELECT (SELECT sum(value1) FROM t2 where value1=value2), max(value1)
           FROM t1
          GROUP BY id1;
     ]],
     {
         -- <aggnested-3.3>
-        0, 2
+        "", 2
         -- </aggnested-3.3>
     })
 
diff --git a/test/sql-tap/identifier_case.test.lua b/test/sql-tap/identifier_case.test.lua
index 9c800dd2c..06be7a3dd 100755
--- a/test/sql-tap/identifier_case.test.lua
+++ b/test/sql-tap/identifier_case.test.lua
@@ -239,14 +239,14 @@ test:do_catchsql_test(
 
 data = {
     { 1,  [[ 'a' < 'b' collate binary ]], {1, "Collation 'BINARY' does not exist"}},
-    { 2,  [[ 'a' < 'b' collate "binary" ]], {0, {1}}},
+    { 2,  [[ 'a' < 'b' collate "binary" ]], {0, {true}}},
     { 3,  [[ 'a' < 'b' collate 'binary' ]], {1, [[Syntax error near ''binary'']]}},
-    { 4,  [[ 'a' < 'b' collate "unicode" ]], {0, {1}}},
-    { 5,  [[ 5 < 'b' collate "unicode" ]], {0, {1}}},
+    { 4,  [[ 'a' < 'b' collate "unicode" ]], {0, {true}}},
+    { 5,  [[ 5 < 'b' collate "unicode" ]], {0, {true}}},
     { 6,  [[ 5 < 'b' collate unicode ]], {1,"Collation 'UNICODE' does not exist"}},
-    { 7,  [[ 5 < 'b' collate "unicode_ci" ]], {0, {1}}},
+    { 7,  [[ 5 < 'b' collate "unicode_ci" ]], {0, {true}}},
     { 8,  [[ 5 < 'b' collate NONE ]], {1, "Collation 'NONE' does not exist"}},
-    { 9,  [[ 5 < 'b' collate "none" ]], {0, {1}}},
+    { 9,  [[ 5 < 'b' collate "none" ]], {0, {true}}},
 }
 
 for _, row in ipairs(data) do
diff --git a/test/sql-tap/tkt3346.test.lua b/test/sql-tap/tkt3346.test.lua
index 269a34f69..ce57a2db0 100755
--- a/test/sql-tap/tkt3346.test.lua
+++ b/test/sql-tap/tkt3346.test.lua
@@ -42,7 +42,7 @@ test:do_test(
     function()
         return test:execsql [[
             SELECT b FROM (SELECT a,b FROM t1) AS x
-             WHERE (SELECT y FROM (SELECT x.b='alice' AS y))=0
+             WHERE (SELECT y FROM (SELECT x.b='alice' AS y))=false
         ]]
     end, {
         -- <tkt3346-1.2>
diff --git a/test/sql/types.result b/test/sql/types.result
index 6bccd39ef..d6c81bed1 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -277,39 +277,39 @@ box.execute("SELECT true = false;")
 ---
 - metadata:
   - name: true = false
-    type: integer
+    type: boolean
   rows:
-  - [0]
+  - [false]
 ...
 box.execute("SELECT true = true;")
 ---
 - metadata:
   - name: true = true
-    type: integer
+    type: boolean
   rows:
-  - [1]
+  - [true]
 ...
 box.execute("SELECT true > false;")
 ---
 - metadata:
   - name: true > false
-    type: integer
+    type: boolean
   rows:
-  - [1]
+  - [true]
 ...
 box.execute("SELECT true < false;")
 ---
 - metadata:
   - name: true < false
-    type: integer
+    type: boolean
   rows:
-  - [0]
+  - [false]
 ...
 box.execute("SELECT null = true;")
 ---
 - metadata:
   - name: null = true
-    type: integer
+    type: boolean
   rows:
   - [null]
 ...
@@ -317,7 +317,7 @@ box.execute("SELECT unknown = true;")
 ---
 - metadata:
   - name: unknown = true
-    type: integer
+    type: boolean
   rows:
   - [null]
 ...
-- 
2.15.1

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

* [tarantool-patches] [PATCH 7/9] sql: make predicates accept and return boolean
  2019-04-14 15:03 [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL Nikita Pettik
                   ` (5 preceding siblings ...)
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 6/9] sql: make comparison predicate return boolean Nikita Pettik
@ 2019-04-14 15:04 ` Nikita Pettik
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 9/9] sql: make <search condition> accept only boolean Nikita Pettik
                   ` (3 subsequent siblings)
  10 siblings, 1 reply; 42+ messages in thread
From: Nikita Pettik @ 2019-04-14 15:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

This patch make following predicates accept and return only values
of type boolean: IN, EXISTS, OR, AND, NOT, BETWEEN, IS (NULL).
In terms of approach, it is enough to patch opcodes implementing these
predicates.

Part of #3723
---
 src/box/sql/expr.c              | 26 ++++++++++++--------------
 src/box/sql/parse.y             |  4 +++-
 src/box/sql/parse_def.c         |  5 +++++
 src/box/sql/select.c            |  2 +-
 src/box/sql/sqlInt.h            |  1 +
 src/box/sql/vdbe.c              | 37 ++++++++++++++-----------------------
 test/sql-tap/cse.test.lua       |  6 +++---
 test/sql-tap/e_select1.test.lua |  2 +-
 test/sql-tap/in1.test.lua       |  6 +++---
 test/sql-tap/misc3.test.lua     |  2 +-
 test/sql-tap/select4.test.lua   |  2 +-
 test/sql-tap/tkt3541.test.lua   |  2 +-
 test/sql/types.result           | 22 +++++++++++++---------
 13 files changed, 59 insertions(+), 58 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index e4de472a8..c5ec55de2 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -102,20 +102,16 @@ sql_expr_type(struct Expr *pExpr)
 	case TK_EQ:
 	case TK_LE:
 	case TK_NE:
-		return FIELD_TYPE_BOOLEAN;
 	case TK_NOT:
 	case TK_AND:
 	case TK_OR:
 	case TK_ISNULL:
 	case TK_NOTNULL:
 	case TK_BETWEEN:
+	case TK_EXISTS:
 	case TK_IN:
-		/*
-		 * FIXME: should be changed to BOOL type
-		 * when it is implemented. Now simply
-		 * return INTEGER.
-		 */
-		return FIELD_TYPE_INTEGER;
+	case TK_IS:
+		return FIELD_TYPE_BOOLEAN;
 	case TK_UMINUS:
 	case TK_UPLUS:
 	case TK_NO:
@@ -1134,7 +1130,10 @@ sql_and_expr_new(struct sql *db, struct Expr *left_expr,
 	} else if (exprAlwaysFalse(left_expr) || exprAlwaysFalse(right_expr)) {
 		sql_expr_delete(db, left_expr, false);
 		sql_expr_delete(db, right_expr, false);
-		return sql_expr_new(db, TK_INTEGER, &sqlIntTokens[0]);
+		struct Expr *f = sql_expr_new(db, TK_FALSE,
+					      &sql_boolean_tokens[0]);
+		f->type = FIELD_TYPE_BOOLEAN;
+		return f;
 	} else {
 		struct Expr *new_expr = sql_expr_new_anon(db, TK_AND);
 		sqlExprAttachSubtrees(db, new_expr, left_expr, right_expr);
@@ -2928,8 +2927,7 @@ sqlCodeSubselect(Parse * pParse,	/* Parsing context */
 				VdbeComment((v, "Init subquery result"));
 			} else {
 				dest.eDest = SRT_Exists;
-				sqlVdbeAddOp2(v, OP_Integer, 0,
-						  dest.iSDParm);
+				sqlVdbeAddOp2(v, OP_Bool, false, dest.iSDParm);
 				VdbeComment((v, "Init EXISTS result"));
 			}
 			if (pSel->pLimit == NULL) {
@@ -3979,14 +3977,14 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			testcase(op == TK_ISNULL);
 			assert(TK_NOTNULL == OP_NotNull);
 			testcase(op == TK_NOTNULL);
-			sqlVdbeAddOp2(v, OP_Integer, 1, target);
+			sqlVdbeAddOp2(v, OP_Bool, true, target);
 			r1 = sqlExprCodeTemp(pParse, pExpr->pLeft,
 						 &regFree1);
 			testcase(regFree1 == 0);
 			addr = sqlVdbeAddOp1(v, op, r1);
 			VdbeCoverageIf(v, op == TK_ISNULL);
 			VdbeCoverageIf(v, op == TK_NOTNULL);
-			sqlVdbeAddOp2(v, OP_Integer, 0, target);
+			sqlVdbeAddOp2(v, OP_Bool, false, target);
 			sqlVdbeJumpHere(v, addr);
 			break;
 		}
@@ -4222,10 +4220,10 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			sqlVdbeAddOp2(v, OP_Null, 0, target);
 			sqlExprCodeIN(pParse, pExpr, destIfFalse,
 					  destIfNull);
-			sqlVdbeAddOp2(v, OP_Integer, 1, target);
+			sqlVdbeAddOp2(v, OP_Bool, true, target);
 			sqlVdbeGoto(v, destIfNull);
 			sqlVdbeResolveLabel(v, destIfFalse);
-			sqlVdbeAddOp2(v, OP_Integer, 0, target);
+			sqlVdbeAddOp2(v, OP_Bool, false, target);
 			sqlVdbeResolveLabel(v, destIfNull);
 			return target;
 		}
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index f64a84948..b520b23d5 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1237,11 +1237,13 @@ expr(A) ::= expr(A) in_op(N) LP exprlist(Y) RP(E). [IN] {
     ** regardless of the value of expr1.
     */
     sql_expr_delete(pParse->db, A.pExpr, false);
-    A.pExpr = sql_expr_new_dequoted(pParse->db, TK_INTEGER, &sqlIntTokens[N]);
+    int tk = N == 0 ? TK_FALSE : TK_TRUE;
+    A.pExpr = sql_expr_new_dequoted(pParse->db, tk, &sql_boolean_tokens[N]);
     if (A.pExpr == NULL) {
       pParse->is_aborted = true;
       return;
     }
+    A.pExpr->type = FIELD_TYPE_BOOLEAN;
   }else if( Y->nExpr==1 ){
     /* Expressions of the form:
     **
diff --git a/src/box/sql/parse_def.c b/src/box/sql/parse_def.c
index 49c76a326..39f4dab15 100644
--- a/src/box/sql/parse_def.c
+++ b/src/box/sql/parse_def.c
@@ -37,6 +37,11 @@ const struct Token sqlIntTokens[] = {
 	{"1", 1, false}
 };
 
+const struct Token sql_boolean_tokens[] = {
+	{"false", 5, true},
+	{"true", 4, true}
+};
+
 void
 sqlTokenInit(struct Token *p, char *z)
 {
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 59f22140c..428e3a92f 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1251,7 +1251,7 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 		/* If any row exist in the result set, record that fact and abort.
 		 */
 	case SRT_Exists:{
-			sqlVdbeAddOp2(v, OP_Integer, 1, iParm);
+			sqlVdbeAddOp2(v, OP_Bool, true, iParm);
 			/* The LIMIT clause will terminate the loop for us */
 			break;
 		}
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 4f62c2782..5bb8a8921 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -4576,6 +4576,7 @@ extern const char sqlStrBINARY[];
 extern const unsigned char sqlUpperToLower[];
 extern const unsigned char sqlCtypeMap[];
 extern const Token sqlIntTokens[];
+extern const struct Token sql_boolean_tokens[];
 extern SQL_WSD struct sqlConfig sqlConfig;
 extern FuncDefHash sqlBuiltinFunctions;
 #ifndef SQL_OMIT_WSD
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 43d4262e5..5b86be522 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2441,14 +2441,10 @@ case OP_Or: {             /* same as TK_OR, in1, in2, out3 */
 	} else if ((pIn1->flags & MEM_Bool) != 0) {
 		v1 = pIn1->u.b;
 	} else {
-		int64_t i;
-		if (sqlVdbeIntValue(pIn1, &i) != 0) {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 sql_value_text(pIn1), "integer");
-			rc = SQL_TARANTOOL_ERROR;
-			goto abort_due_to_error;
-		}
-		v1 = i != 0;
+		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+			 sql_value_text(pIn1), "boolean");
+		rc = SQL_TARANTOOL_ERROR;
+		goto abort_due_to_error;
 	}
 	pIn2 = &aMem[pOp->p2];
 	if (pIn2->flags & MEM_Null) {
@@ -2456,14 +2452,10 @@ case OP_Or: {             /* same as TK_OR, in1, in2, out3 */
 	} else if ((pIn2->flags & MEM_Bool) != 0) {
 		v2 = pIn2->u.b;
 	} else {
-		int64_t i;
-		if (sqlVdbeIntValue(pIn2, &i) != 0) {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 sql_value_text(pIn2), "integer");
-			rc = SQL_TARANTOOL_ERROR;
-			goto abort_due_to_error;
-		}
-		v2 = i != 0;
+		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+			 sql_value_text(pIn2), "boolean");
+		rc = SQL_TARANTOOL_ERROR;
+		goto abort_due_to_error;
 	}
 	if (pOp->opcode==OP_And) {
 		static const unsigned char and_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 };
@@ -2476,8 +2468,8 @@ case OP_Or: {             /* same as TK_OR, in1, in2, out3 */
 	if (v1==2) {
 		MemSetTypeFlag(pOut, MEM_Null);
 	} else {
-		pOut->u.i = v1;
-		MemSetTypeFlag(pOut, MEM_Int);
+		pOut->u.b = v1;
+		MemSetTypeFlag(pOut, MEM_Bool);
 	}
 	break;
 }
@@ -2494,15 +2486,14 @@ case OP_Not: {                /* same as TK_NOT, in1, out2 */
 	pOut = &aMem[pOp->p2];
 	sqlVdbeMemSetNull(pOut);
 	if ((pIn1->flags & MEM_Null)==0) {
-		int64_t i;
-		if (sqlVdbeIntValue(pIn1, &i) != 0) {
+		if ((pIn1->flags & MEM_Bool) == 0) {
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 sql_value_text(pIn1), "integer");
+				 sql_value_text(pIn1), "boolean");
 			rc = SQL_TARANTOOL_ERROR;
 			goto abort_due_to_error;
 		}
-		pOut->flags = MEM_Int;
-		pOut->u.i = !i;
+		pOut->flags = MEM_Bool;
+		pOut->u.b = ! pIn1->u.b;
 	}
 	break;
 }
diff --git a/test/sql-tap/cse.test.lua b/test/sql-tap/cse.test.lua
index 4b25f936d..39c1cc4ca 100755
--- a/test/sql-tap/cse.test.lua
+++ b/test/sql-tap/cse.test.lua
@@ -31,7 +31,7 @@ test:do_test(
             INSERT INTO t1 VALUES(2,21,22,23,24,25);
         ]]
         return test:execsql [[
-            SELECT b, -b, ~b, NOT b, NOT NOT b, b-b, b+b, b*b, b/b, b FROM t1
+            SELECT b, -b, ~b, NOT CAST(b AS BOOLEAN), NOT NOT CAST(b AS BOOLEAN), b-b, b+b, b*b, b/b, b FROM t1
         ]]
     end, {
         -- <cse-1.1>
@@ -132,7 +132,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "cse-1.7",
     [[
-        SELECT a, -a, ~a, NOT a, NOT NOT a, a-a, a+a, a*a, a/a, a FROM t1
+        SELECT a, -a, ~a, NOT CAST(a AS BOOLEAN), NOT NOT CAST(a AS BOOLEAN), a-a, a+a, a*a, a/a, a FROM t1
     ]], {
         -- <cse-1.7>
         1, -1, -2, 0, 1, 0, 2, 1, 1, 1, 2, -2, -3, 0, 1, 0, 4, 4, 1, 2
@@ -152,7 +152,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "cse-1.9",
     [[
-        SELECT NOT b, ~b, NOT NOT b, b FROM t1
+        SELECT NOT CAST(b AS BOOLEAN), ~b, NOT NOT CAST(b AS BOOLEAN), b FROM t1
     ]], {
         -- <cse-1.9>
         0, -12, 1, 11, 0, -22, 1, 21
diff --git a/test/sql-tap/e_select1.test.lua b/test/sql-tap/e_select1.test.lua
index 8e9a2bb23..970eeeed9 100755
--- a/test/sql-tap/e_select1.test.lua
+++ b/test/sql-tap/e_select1.test.lua
@@ -897,7 +897,7 @@ test:do_select_tests(
 
         {"3", "SELECT sum(b+1) FROM z1 NATURAL LEFT JOIN z3", {-43.06}},
         {"4", "SELECT sum(b+2) FROM z1 NATURAL LEFT JOIN z3", {-38.06}},
-        {"5", "SELECT sum(b IS NOT NULL) FROM z1 NATURAL LEFT JOIN z3", {5}},
+        {"5", "SELECT sum(CAST(b IS NOT NULL AS INTEGER)) FROM z1 NATURAL LEFT JOIN z3", {5}},
     })
 
 -- EVIDENCE-OF: R-26684-40576 Each non-aggregate expression in the
diff --git a/test/sql-tap/in1.test.lua b/test/sql-tap/in1.test.lua
index 835c10dd5..08a7c3628 100755
--- a/test/sql-tap/in1.test.lua
+++ b/test/sql-tap/in1.test.lua
@@ -100,7 +100,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "in-1.7",
     [[
-        SELECT a+ 100*(a BETWEEN 1 and 3) FROM t1 ORDER BY b
+        SELECT a+ 100*CAST((a BETWEEN 1 and 3) AS INTEGER) FROM t1 ORDER BY b
     ]], {
         -- <in-1.7>
         101, 102, 103, 4, 5, 6, 7, 8, 9, 10
@@ -157,7 +157,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "in-2.5",
     [[
-        SELECT a+100*(b IN (8,16,24)) FROM t1 ORDER BY b
+        SELECT a+100*(CAST(b IN (8,16,24) AS INTEGER)) FROM t1 ORDER BY b
     ]], {
         -- <in-2.5>
         1, 2, 103, 104, 5, 6, 7, 8, 9, 10
@@ -253,7 +253,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "in-3.3",
     [[
-        SELECT a + 100*(b IN (SELECT b FROM t1 WHERE a<5)) FROM t1 ORDER BY b
+        SELECT a + 100*(CAST(b IN (SELECT b FROM t1 WHERE a<5) AS INTEGER)) FROM t1 ORDER BY b
     ]], {
         -- <in-3.3>
         101, 102, 103, 104, 5, 6, 7, 8, 9, 10
diff --git a/test/sql-tap/misc3.test.lua b/test/sql-tap/misc3.test.lua
index eed40f2cd..d0e45e872 100755
--- a/test/sql-tap/misc3.test.lua
+++ b/test/sql-tap/misc3.test.lua
@@ -511,7 +511,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "misc-8.2",
     [[
-        SELECT count(*) FROM t3 WHERE 1+(b IN ('abc','xyz'))==2
+        SELECT count(*) FROM t3 WHERE 1+CAST((b IN ('abc','xyz')) AS INTEGER)==2
     ]], {
         -- <misc-8.2>
         2
diff --git a/test/sql-tap/select4.test.lua b/test/sql-tap/select4.test.lua
index 3aafedb4c..b78091b28 100755
--- a/test/sql-tap/select4.test.lua
+++ b/test/sql-tap/select4.test.lua
@@ -1449,7 +1449,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select4-14.15",
     [[
-        SELECT * FROM (SELECT 123), (SELECT 456) ON likely(0 OR 1) OR 0;
+        SELECT * FROM (SELECT 123), (SELECT 456) ON likely(false OR true) OR false;
     ]], {
         -- <select4-14.15>
         123, 456
diff --git a/test/sql-tap/tkt3541.test.lua b/test/sql-tap/tkt3541.test.lua
index 08a9be557..05247728d 100755
--- a/test/sql-tap/tkt3541.test.lua
+++ b/test/sql-tap/tkt3541.test.lua
@@ -39,7 +39,7 @@ test:do_test(
     "tkt3541-1.2",
     function()
         return test:execsql [[
-            SELECT CASE NOT max(x) WHEN min(x) THEN 1 ELSE max(x) END FROM t1;
+            SELECT CASE max(x) = 0 WHEN min(x) > 0 THEN 1 ELSE max(x) END FROM t1;
         ]]
     end, {
         -- <tkt3541-1.2>
diff --git a/test/sql/types.result b/test/sql/types.result
index d6c81bed1..dc472d578 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -337,17 +337,17 @@ box.execute("SELECT true IN (1, 'abc', true)")
 ---
 - metadata:
   - name: true IN (1, 'abc', true)
-    type: integer
+    type: boolean
   rows:
-  - [1]
+  - [true]
 ...
 box.execute("SELECT true IN (1, 'abc', false)")
 ---
 - metadata:
   - name: true IN (1, 'abc', false)
-    type: integer
+    type: boolean
   rows:
-  - [0]
+  - [false]
 ...
 box.execute("SELECT 1 LIMIT true;")
 ---
@@ -377,7 +377,11 @@ box.execute("SELECT false / 0;")
 ...
 box.execute("SELECT not true;")
 ---
-- error: 'Type mismatch: can not convert true to integer'
+- metadata:
+  - name: not true
+    type: boolean
+  rows:
+  - [false]
 ...
 box.execute("SELECT ~true;")
 ---
@@ -399,17 +403,17 @@ box.execute("SELECT true and false;")
 ---
 - metadata:
   - name: true and false
-    type: integer
+    type: boolean
   rows:
-  - [0]
+  - [false]
 ...
 box.execute("SELECT true or unknown;")
 ---
 - metadata:
   - name: true or unknown
-    type: integer
+    type: boolean
   rows:
-  - [1]
+  - [true]
 ...
 box.execute("CREATE TABLE t (id INT PRIMARY KEY, b BOOLEAN);")
 ---
-- 
2.15.1

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

* [tarantool-patches] [PATCH 9/9] sql: make <search condition> accept only boolean
  2019-04-14 15:03 [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL Nikita Pettik
                   ` (6 preceding siblings ...)
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 7/9] sql: make predicates accept and " Nikita Pettik
@ 2019-04-14 15:04 ` Nikita Pettik
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
       [not found] ` <b2a84f129c2343d3da3311469cbb7b20488a21c2.1555252410.git.korablev@tarantool.org>
                   ` (2 subsequent siblings)
  10 siblings, 1 reply; 42+ messages in thread
From: Nikita Pettik @ 2019-04-14 15:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

<search condition> is a predicate used as a part of WHERE and
JOIN clauses. ANSI SQL states that <search condition> must
accept only boolean arguments. In our SQL it is implemented as
bytecode instruction OP_If which in turn carries out logic of
conditional jump. Since it can be involved in executing other routines
different from <search condition>, we pass to it additional argument
when generating bytecode for WHERE and JOIN clauses. When VDBE performs
OP_If and detects such flag, it checks passed argument to be boolean.

Closes #3723
---
 src/box/sql/expr.c               | 49 ++++++++++++++++++++-----------------
 src/box/sql/sqlInt.h             |  2 ++
 src/box/sql/vdbe.c               | 15 ++++++++++--
 src/box/sql/where.c              |  3 ++-
 src/box/sql/wherecode.c          |  3 ++-
 test/sql-tap/e_delete.test.lua   |  8 +++----
 test/sql-tap/e_select1.test.lua  | 52 ++++++++++++++++++++--------------------
 test/sql-tap/func.test.lua       |  2 +-
 test/sql-tap/in1.test.lua        |  2 +-
 test/sql-tap/in3.test.lua        |  2 +-
 test/sql-tap/join5.test.lua      | 20 ++++++++--------
 test/sql-tap/limit.test.lua      |  2 +-
 test/sql-tap/resolver01.test.lua |  2 +-
 test/sql-tap/select2.test.lua    |  8 +++----
 test/sql-tap/select6.test.lua    |  4 ++--
 test/sql-tap/select9.test.lua    |  6 ++---
 test/sql-tap/subquery.test.lua   |  4 ++--
 test/sql-tap/tkt2832.test.lua    |  2 +-
 18 files changed, 103 insertions(+), 83 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index c5ec55de2..9a9083392 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -727,10 +727,12 @@ codeVectorCompare(Parse * pParse,	/* Code generator context */
 		}
 		if (opx == TK_EQ) {
 			sqlVdbeAddOp2(v, OP_IfNot, dest, addrDone);
+			sqlVdbeChangeP5(v, OPFLAG_BOOLREQ);
 			VdbeCoverage(v);
 			p5 |= SQL_KEEPNULL;
 		} else if (opx == TK_NE) {
 			sqlVdbeAddOp2(v, OP_If, dest, addrDone);
+			sqlVdbeChangeP5(v, OPFLAG_BOOLREQ);
 			VdbeCoverage(v);
 			p5 |= SQL_KEEPNULL;
 		} else {
@@ -1083,16 +1085,11 @@ sqlPExprAddSelect(Parse * pParse, Expr * pExpr, Select * pSelect)
 	}
 }
 
-/*
+/**
  * If the expression is always either TRUE or FALSE (respectively),
- * then return 1.  If one cannot determine the truth value of the
+ * then return 1. If one cannot determine the truth value of the
  * expression at compile-time return 0.
  *
- * This is an optimization.  If is OK to return 0 here even if
- * the expression really is always false or false (a false negative).
- * But it is a bug to return 1 if the expression might have different
- * boolean values in different circumstances (a false positive.)
- *
  * Note that if the expression is part of conditional for a
  * LEFT JOIN, then we cannot determine at compile-time whether or not
  * is it true or false, so always return 0.
@@ -1100,23 +1097,21 @@ sqlPExprAddSelect(Parse * pParse, Expr * pExpr, Select * pSelect)
 static int
 exprAlwaysTrue(Expr * p)
 {
-	int v = 0;
 	if (ExprHasProperty(p, EP_FromJoin))
 		return 0;
-	if (!sqlExprIsInteger(p, &v))
-		return 0;
-	return v != 0;
+	if (p->op == TK_TRUE)
+		return 1;
+	return 0;
 }
 
 static int
 exprAlwaysFalse(Expr * p)
 {
-	int v = 0;
 	if (ExprHasProperty(p, EP_FromJoin))
 		return 0;
-	if (!sqlExprIsInteger(p, &v))
-		return 0;
-	return v == 0;
+	if (p->op == TK_FALSE)
+		return 1;
+	return 0;
 }
 
 struct Expr *
@@ -4744,7 +4739,7 @@ exprCodeBetween(Parse * pParse,	/* Parsing and code generating context */
  * continues straight thru if the expression is false.
  *
  * If the expression evaluates to NULL (neither true nor false), then
- * take the jump if the jumpIfNull flag is SQL_JUMPIFNULL.
+ * take the jump if the flag is SQL_JUMPIFNULL.
  *
  * This code depends on the fact that certain token values (ex: TK_EQ)
  * are the same as opcode values (ex: OP_Eq) that implement the corresponding
@@ -4753,14 +4748,14 @@ exprCodeBetween(Parse * pParse,	/* Parsing and code generating context */
  * below verify that the numbers are aligned correctly.
  */
 void
-sqlExprIfTrue(Parse * pParse, Expr * pExpr, int dest, int jumpIfNull)
+sqlExprIfTrue(Parse * pParse, Expr * pExpr, int dest, int flags)
 {
 	Vdbe *v = pParse->pVdbe;
 	int op = 0;
 	int regFree1 = 0;
 	int regFree2 = 0;
 	int r1, r2;
-
+	int jumpIfNull = flags & SQL_JUMPIFNULL;
 	assert(jumpIfNull == SQL_JUMPIFNULL || jumpIfNull == 0);
 	if (NEVER(v == 0))
 		return;		/* Existence of VDBE checked by caller */
@@ -4896,18 +4891,22 @@ sqlExprIfTrue(Parse * pParse, Expr * pExpr, int dest, int jumpIfNull)
  * continues straight thru if the expression is true.
  *
  * If the expression evaluates to NULL (neither true nor false) then
- * jump if jumpIfNull is SQL_JUMPIFNULL or fall through if jumpIfNull
- * is 0.
+ * jump if flags contains SQL_JUMPIFNULL or fall through if it doesn't.
+ *
+ * IF flags contains SQL_BOOLREQ then OP_If(Not) is supplied with
+ * flag OPFLAG_BOOLREQ which forces additional verification of
+ * its arguments. It is required to make sure that searching
+ * condition is boolean (to disallow queries like ... WHERE 1+1;).
  */
 void
-sqlExprIfFalse(Parse * pParse, Expr * pExpr, int dest, int jumpIfNull)
+sqlExprIfFalse(Parse * pParse, Expr * pExpr, int dest, int flags)
 {
 	Vdbe *v = pParse->pVdbe;
 	int op = 0;
 	int regFree1 = 0;
 	int regFree2 = 0;
 	int r1, r2;
-
+	int jumpIfNull = flags & SQL_JUMPIFNULL;
 	assert(jumpIfNull == SQL_JUMPIFNULL || jumpIfNull == 0);
 	if (NEVER(v == 0))
 		return;		/* Existence of VDBE checked by caller */
@@ -5072,6 +5071,12 @@ sqlExprIfFalse(Parse * pParse, Expr * pExpr, int dest, int jumpIfNull)
 							 &regFree1);
 				sqlVdbeAddOp3(v, OP_IfNot, r1, dest,
 						  jumpIfNull != 0);
+				/*
+				 * Make sure that search condition
+				 * under WHERE clause returns boolean.
+				 */
+				if ((flags & SQL_BOOLREQ) != 0)
+					sqlVdbeChangeP5(v, OPFLAG_BOOLREQ);
 				VdbeCoverage(v);
 				testcase(regFree1 == 0);
 				testcase(jumpIfNull == 0);
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 10bdb0597..488ef639f 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1782,6 +1782,7 @@ struct Savepoint {
 #define SQL_KEEPNULL     0x40	/* Used by vector == or <> */
 #define SQL_NULLEQ       0x80	/* NULL=NULL */
 #define SQL_NOTNULL      0x90	/* Assert that operands are never NULL */
+#define SQL_BOOLREQ      0x100	/* Argument passed to OP_If must be boolean */
 
 /**
  * Return logarithm of tuple count in space.
@@ -2762,6 +2763,7 @@ struct Parse {
 #define OPFLAG_SYSTEMSP      0x20	/* OP_Open**: set if space pointer
 					 * points to system space.
 					 */
+#define OPFLAG_BOOLREQ       0x1000	/* OP_IF(Not): operand must be boolean. */
 
 /**
  * Prepare vdbe P5 flags for OP_{IdxInsert, IdxReplace, Update}
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 5b86be522..717f56803 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2541,13 +2541,18 @@ case OP_Once: {             /* jump */
 	break;
 }
 
-/* Opcode: If P1 P2 P3 * *
+/* Opcode: If P1 P2 P3 * P5
  *
  * Jump to P2 if the value in register P1 is true.  The value
  * is considered true if it is numeric and non-zero.  If the value
  * in P1 is NULL then take the jump if and only if P3 is non-zero.
+ *
+ * In case P5 contains BOOLREQ flag, then argument is supposed
+ * to be BOOLEAN. Otherwise, an error is raised. Such check is
+ * required to restrict <search condition> used in WHERE and
+ * JOIN clauses allowing only boolean values.
  */
-/* Opcode: IfNot P1 P2 P3 * *
+/* Opcode: IfNot P1 P2 P3 * P5
  *
  * Jump to P2 if the value in register P1 is False.  The value
  * is considered false if it has a numeric value of zero.  If the value
@@ -2562,6 +2567,12 @@ case OP_IfNot: {            /* jump, in1 */
 	} else if ((pIn1->flags & MEM_Bool) != 0) {
 		c = pOp->opcode==OP_IfNot ? ! pIn1->u.b : pIn1->u.b;
 	} else {
+		if (pOp->p5 == OPFLAG_BOOLREQ) {
+			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+				 sql_value_text(pIn1), "boolean");
+			rc = SQL_TARANTOOL_ERROR;
+			goto abort_due_to_error;
+		}
 		double v;
 		if (sqlVdbeRealValue(pIn1, &v) != 0) {
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 19ee2d03a..93020b148 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -4349,7 +4349,8 @@ sqlWhereBegin(Parse * pParse,	/* The parser context */
 		if (nTabList == 0
 		    || sqlExprIsConstantNotJoin(sWLB.pWC->a[ii].pExpr)) {
 			sqlExprIfFalse(pParse, sWLB.pWC->a[ii].pExpr,
-					   pWInfo->iBreak, SQL_JUMPIFNULL);
+				       pWInfo->iBreak,
+				       SQL_JUMPIFNULL | SQL_BOOLREQ);
 			sWLB.pWC->a[ii].wtFlags |= TERM_CODED;
 		}
 	}
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index a453fe979..5ee2efce7 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -1613,7 +1613,8 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about the W
 			 */
 			continue;
 		}
-		sqlExprIfFalse(pParse, pE, addrCont, SQL_JUMPIFNULL);
+		sqlExprIfFalse(pParse, pE, addrCont,
+			       SQL_JUMPIFNULL | SQL_BOOLREQ);
 		if (skipLikeAddr)
 			sqlVdbeJumpHere(v, skipLikeAddr);
 		pTerm->wtFlags |= TERM_CODED;
diff --git a/test/sql-tap/e_delete.test.lua b/test/sql-tap/e_delete.test.lua
index a58dc87c7..374a7d3e4 100755
--- a/test/sql-tap/e_delete.test.lua
+++ b/test/sql-tap/e_delete.test.lua
@@ -89,15 +89,15 @@ test:do_delete_tests("e_delete-1.1", {
 -- NULL are retained.
 --
 test:do_delete_tests("e_delete-1.2", {
-    {1, "DELETE FROM t3 WHERE 1       ; SELECT x FROM t3", {}},
-    {2, "DELETE FROM t4 WHERE 0  ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
-    {3, "DELETE FROM t4 WHERE 0.0     ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
+    {1, "DELETE FROM t3 WHERE true       ; SELECT x FROM t3", {}},
+    {2, "DELETE FROM t4 WHERE false  ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
+    {3, "DELETE FROM t4 WHERE false    ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
     {4, "DELETE FROM t4 WHERE NULL    ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
     {5, "DELETE FROM t4 WHERE y!='two'; SELECT x FROM t4", {2}},
     {6, "DELETE FROM t4 WHERE y='two' ; SELECT x FROM t4", {}},
     {7, "DELETE FROM t5 WHERE x=(SELECT max(x) FROM t5);SELECT x FROM t5", {1, 2, 3, 4}},
     {8, "DELETE FROM t5 WHERE (SELECT max(x) FROM t4)  ;SELECT x FROM t5", {1, 2, 3, 4}},
-    {9, "DELETE FROM t5 WHERE (SELECT max(x) FROM t6)  ;SELECT x FROM t5", {}},
+    {9, "DELETE FROM t5 WHERE (SELECT max(x) FROM t6) != 0  ;SELECT x FROM t5", {}},
     {10, "DELETE FROM t6 WHERE y>'seven' ; SELECT y FROM t6", {"one", "four", "five"}},
 })
 
diff --git a/test/sql-tap/e_select1.test.lua b/test/sql-tap/e_select1.test.lua
index 970eeeed9..e47b0f43d 100755
--- a/test/sql-tap/e_select1.test.lua
+++ b/test/sql-tap/e_select1.test.lua
@@ -136,13 +136,13 @@ test:do_select_tests(
         {"1100.1", "SELECT DISTINCT a, b, a||b FROM t1 ", {"a", "one", "aone", "b", "two", "btwo", "c", "three", "cthree"}},
         {"1200.1", "SELECT ALL a, b, a||b FROM t1 ", {"a", "one", "aone", "b", "two", "btwo", "c", "three", "cthree"}},
 
-        {"0010.1", "SELECT 1, 2, 3 WHERE 1 ", {1, 2, 3}},
-        {"0010.2", "SELECT 1, 2, 3 WHERE 0 ", {}},
+        {"0010.1", "SELECT 1, 2, 3 WHERE true ", {1, 2, 3}},
+        {"0010.2", "SELECT 1, 2, 3 WHERE false ", {}},
         {"0010.3", "SELECT 1, 2, 3 WHERE NULL ", {}},
 
-        {"1010.1", "SELECT DISTINCT 1, 2, 3 WHERE 1 ", {1, 2, 3}},
+        {"1010.1", "SELECT DISTINCT 1, 2, 3 WHERE true ", {1, 2, 3}},
 
-        {"2010.1", "SELECT ALL 1, 2, 3 WHERE 1 ", {1, 2, 3}},
+        {"2010.1", "SELECT ALL 1, 2, 3 WHERE true ", {1, 2, 3}},
 
         {"0110.1", "SELECT a, b, a||b FROM t1 WHERE a!='x' ", {"a", "one", "aone", "b", "two", "btwo", "c", "three", "cthree"}},
         {"0110.2", "SELECT a, b, a||b FROM t1 WHERE a=='x'", {}},
@@ -175,28 +175,28 @@ test:do_select_tests(
         {"2102.1", "SELECT ALL count(*), max(a) FROM t1 GROUP BY b HAVING count(*)=1", {1, "a", 1, "c", 1, "b"}},
         {"2102.2", "SELECT ALL count(*), max(a) FROM t1 GROUP BY b HAVING count(*)=2", {}},
 
-        {"0011.1", "SELECT 1, 2, 3 WHERE 1 GROUP BY 2", {1, 2, 3}},
-        {"0012.1", "SELECT 1, 2, 3 WHERE 0 GROUP BY 2 HAVING count(*)=1", {}},
-        {"0012.2", "SELECT 1, 2, 3 WHERE 0 GROUP BY 2 HAVING count(*)>1", {}},
+        {"0011.1", "SELECT 1, 2, 3 WHERE true GROUP BY 2", {1, 2, 3}},
+        {"0012.1", "SELECT 1, 2, 3 WHERE false GROUP BY 2 HAVING count(*)=1", {}},
+        {"0012.2", "SELECT 1, 2, 3 WHERE false GROUP BY 2 HAVING count(*)>1", {}},
 
-        {"1011.1", "SELECT DISTINCT 1, 2, 3 WHERE 0 GROUP BY 2", {}},
-        {"1012.1", "SELECT DISTINCT 1, 2, 3 WHERE 1 GROUP BY 2 HAVING count(*)=1", {1, 2, 3}},
+        {"1011.1", "SELECT DISTINCT 1, 2, 3 WHERE false GROUP BY 2", {}},
+        {"1012.1", "SELECT DISTINCT 1, 2, 3 WHERE true GROUP BY 2 HAVING count(*)=1", {1, 2, 3}},
         {"1012.2", "SELECT DISTINCT 1, 2, 3 WHERE NULL GROUP BY 2 HAVING count(*)>1", {}},
 
-        {"2011.1", "SELECT ALL 1, 2, 3 WHERE 1 GROUP BY 2", {1, 2, 3}},
-        {"2012.1", "SELECT ALL 1, 2, 3 WHERE 0 GROUP BY 2 HAVING count(*)=1", {}},
-        {"2012.2", "SELECT ALL 1, 2, 3 WHERE 2 GROUP BY 2 HAVING count(*)>1", {}},
+        {"2011.1", "SELECT ALL 1, 2, 3 WHERE true GROUP BY 2", {1, 2, 3}},
+        {"2012.1", "SELECT ALL 1, 2, 3 WHERE false GROUP BY 2 HAVING count(*)=1", {}},
+        {"2012.2", "SELECT ALL 1, 2, 3 WHERE true GROUP BY 2 HAVING count(*)>1", {}},
 
         {"0111.1", "SELECT count(*), max(a) FROM t1 WHERE a='a' GROUP BY b", {1, "a"}},
         {"0112.1", "SELECT count(*), max(a) FROM t1 WHERE a='c' GROUP BY b HAVING count(*)=1", {1, "c"}},
-        {"0112.2", "SELECT count(*), max(a) FROM t1 WHERE 0 GROUP BY b HAVING count(*)=2", { }},
+        {"0112.2", "SELECT count(*), max(a) FROM t1 WHERE false GROUP BY b HAVING count(*)=2", { }},
         {"1111.1", "SELECT DISTINCT count(*), max(a) FROM t1 WHERE a<'c' GROUP BY b", {1, "a", 1, "b"}},
         {"1112.1", "SELECT DISTINCT count(*), max(a) FROM t1 WHERE a>'a' GROUP BY b HAVING count(*)=1", {1, "c", 1, "b"}},
-        {"1112.2", "SELECT DISTINCT count(*), max(a) FROM t1 WHERE 0 GROUP BY b HAVING count(*)=2", { }},
+        {"1112.2", "SELECT DISTINCT count(*), max(a) FROM t1 WHERE false GROUP BY b HAVING count(*)=2", { }},
 
         {"2111.1", "SELECT ALL count(*), max(a) FROM t1 WHERE b>'one' GROUP BY b", {1, "c", 1, "b"}},
         {"2112.1", "SELECT ALL count(*), max(a) FROM t1 WHERE a!='b' GROUP BY b HAVING count(*)=1", {1, "a", 1, "c"}},
-        {"2112.2", "SELECT ALL count(*), max(a) FROM t1 WHERE 0 GROUP BY b HAVING count(*)=2", { }},
+        {"2112.2", "SELECT ALL count(*), max(a) FROM t1 WHERE false GROUP BY b HAVING count(*)=2", { }},
     })
 
 -- -- syntax diagram result-column
@@ -294,8 +294,8 @@ test:do_select_tests(
         {"2", "SELECT 'abc' WHERE NULL", {}},
         {"3", "SELECT NULL", {""}},
         {"4", "SELECT count(*)", {1}},
-        {"5", "SELECT count(*) WHERE 0", {0}},
-        {"6", "SELECT count(*) WHERE 1", {1}},
+        {"5", "SELECT count(*) WHERE false", {0}},
+        {"6", "SELECT count(*) WHERE true", {1}},
     })
 
 --
@@ -448,15 +448,15 @@ test:do_select_tests(
 -- true are included from the dataset.
 --
 local data ={
-    {"1"," SELECT * FROM t1 JOIN_PATTERN t2 ON (1) ",t1_cross_t2},
-    {"2"," SELECT * FROM t1 JOIN_PATTERN t2 ON (0) ",{}},
+    {"1"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
+    {"2"," SELECT * FROM t1 JOIN_PATTERN t2 ON (false) ",{}},
     {"3"," SELECT * FROM t1 JOIN_PATTERN t2 ON (NULL) ",{}},
-    {"6"," SELECT * FROM t1 JOIN_PATTERN t2 ON (0.9) ",t1_cross_t2},
-    {"7"," SELECT * FROM t1 JOIN_PATTERN t2 ON ('0.9') ",t1_cross_t2},
-    {"8"," SELECT * FROM t1 JOIN_PATTERN t2 ON (0.0) ",{}},
+    {"6"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
+    {"7"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
+    {"8"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
     {"9"," SELECT t1.b, t2.b FROM t1 JOIN_PATTERN t2 ON (t1.a = t2.a) ",{"one", "I", "two", "II", "three", "III"}},
     {"10"," SELECT t1.b, t2.b FROM t1 JOIN_PATTERN t2 ON (t1.a = 'a') ",{"one", "I", "one", "II", "one", "III"}},
-    {"11"," SELECT t1.b, t2.b FROM t1 JOIN_PATTERN t2 ON (CASE WHEN t1.a = 'a' THEN NULL ELSE 1 END)",
+    {"11"," SELECT t1.b, t2.b FROM t1 JOIN_PATTERN t2 ON (CASE WHEN t1.a = 'a' THEN NULL ELSE true END)",
     {"two", "I", "two", "II", "two", "III", "three", "I", "three", "II", "three", "III"}},
 }
 for _, val in ipairs(data) do
@@ -725,7 +725,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "e_select-3.2.3",
     [[
-        SELECT k FROM x1 NATURAL JOIN x2 WHERE x2.k
+        SELECT k FROM x1 NATURAL JOIN x2 WHERE x2.k <> 0
     ]], {
         -- <e_select-3.2.3>
         3
@@ -735,7 +735,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "e_select-3.2.4",
     [[
-        SELECT k FROM x1 NATURAL JOIN x2 WHERE x2.k-3
+        SELECT k FROM x1 NATURAL JOIN x2 WHERE x2.k-3 <> 0
     ]], {
         -- <e_select-3.2.4>
 
@@ -952,7 +952,7 @@ test:do_select_tests(
 test:do_select_tests(
     "e_select-4.7",
     {
-        {"1", "SELECT one, two, count(*) FROM a1 WHERE 0", {"", "", 0}},
+        {"1", "SELECT one, two, count(*) FROM a1 WHERE false", {"", "", 0}},
         {"2", "SELECT sum(two), * FROM a1, a2 WHERE three>5", {"", "", "", "", ""}},
         {"3", "SELECT max(one) IS NULL, one IS NULL, two IS NULL FROM a1 WHERE two=7", {1, 1, 1}},
     })
diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
index 2df832b1b..0cdcdfde6 100755
--- a/test/sql-tap/func.test.lua
+++ b/test/sql-tap/func.test.lua
@@ -1739,7 +1739,7 @@ test:do_catchsql_test(
 test:do_execsql_test(
     "func-19.1",
     [[
-        SELECT match(a,b) FROM t1 WHERE 0;
+        SELECT match(a,b) FROM t1 WHERE false;
     ]], {
         -- <func-19.1>
         
diff --git a/test/sql-tap/in1.test.lua b/test/sql-tap/in1.test.lua
index 08a7c3628..76112cff9 100755
--- a/test/sql-tap/in1.test.lua
+++ b/test/sql-tap/in1.test.lua
@@ -207,7 +207,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "in-2.10",
     [[
-        SELECT a FROM t1 WHERE min(0,b IN (a,30))
+        SELECT a FROM t1 WHERE min(0,b IN (a,30)) <> 0
     ]], {
         -- <in-2.10>
         
diff --git a/test/sql-tap/in3.test.lua b/test/sql-tap/in3.test.lua
index 988a9b2a7..a5e31f8a7 100755
--- a/test/sql-tap/in3.test.lua
+++ b/test/sql-tap/in3.test.lua
@@ -116,7 +116,7 @@ test:do_test(
 test:do_test(
     "in3-1.8",
     function()
-        return exec_neph(" SELECT a FROM t1 WHERE a IN (SELECT a FROM t1 WHERE 1); ")
+        return exec_neph(" SELECT a FROM t1 WHERE a IN (SELECT a FROM t1 WHERE true); ")
     end, {
         -- <in3-1.8>
         1, 1, 3, 5
diff --git a/test/sql-tap/join5.test.lua b/test/sql-tap/join5.test.lua
index 000370d84..c18be5230 100755
--- a/test/sql-tap/join5.test.lua
+++ b/test/sql-tap/join5.test.lua
@@ -98,7 +98,7 @@ test:do_test(
             INSERT INTO xy VALUES(2,3);
             INSERT INTO xy VALUES(NULL,1);
         ]]
-        return test:execsql "SELECT * FROM xy LEFT JOIN ab ON 0"
+        return test:execsql "SELECT * FROM xy LEFT JOIN ab ON false"
     end, {
         -- <join5-2.1>
         "", 1, "", "", 2, 3, "", ""
@@ -108,7 +108,7 @@ test:do_test(
 test:do_execsql_test(
     "join5-2.2",
     [[
-        SELECT * FROM xy LEFT JOIN ab ON 1
+        SELECT * FROM xy LEFT JOIN ab ON true
     ]], {
         -- <join5-2.2>
         "", 1, 1, 2, "", 1, 3, "", 2, 3, 1, 2, 2, 3, 3, ""
@@ -128,7 +128,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "join5-2.4",
     [[
-        SELECT * FROM xy LEFT JOIN ab ON 0 WHERE 0
+        SELECT * FROM xy LEFT JOIN ab ON false WHERE false
     ]], {
         -- <join5-2.4>
         
@@ -138,7 +138,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "join5-2.5",
     [[
-        SELECT * FROM xy LEFT JOIN ab ON 1 WHERE 0
+        SELECT * FROM xy LEFT JOIN ab ON true WHERE false
     ]], {
         -- <join5-2.5>
         
@@ -148,7 +148,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "join5-2.6",
     [[
-        SELECT * FROM xy LEFT JOIN ab ON NULL WHERE 0
+        SELECT * FROM xy LEFT JOIN ab ON NULL WHERE false
     ]], {
         -- <join5-2.6>
         
@@ -158,7 +158,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "join5-2.7",
     [[
-        SELECT * FROM xy LEFT JOIN ab ON 0 WHERE 1
+        SELECT * FROM xy LEFT JOIN ab ON false WHERE true
     ]], {
         -- <join5-2.7>
         "", 1, "", "", 2, 3, "", ""
@@ -168,7 +168,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "join5-2.8",
     [[
-        SELECT * FROM xy LEFT JOIN ab ON 1 WHERE 1
+        SELECT * FROM xy LEFT JOIN ab ON true WHERE true
     ]], {
         -- <join5-2.8>
         "",1 ,1, 2, "", 1, 3, "", 2, 3, 1, 2, 2, 3, 3, ""
@@ -178,7 +178,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "join5-2.9",
     [[
-        SELECT * FROM xy LEFT JOIN ab ON NULL WHERE 1
+        SELECT * FROM xy LEFT JOIN ab ON NULL WHERE true
     ]], {
         -- <join5-2.9>
         "", 1, "", "", 2, 3, "", ""
@@ -188,7 +188,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "join5-2.10",
     [[
-        SELECT * FROM xy LEFT JOIN ab ON 0 WHERE NULL
+        SELECT * FROM xy LEFT JOIN ab ON false WHERE NULL
     ]], {
         -- <join5-2.10>
         
@@ -198,7 +198,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "join5-2.11",
     [[
-        SELECT * FROM xy LEFT JOIN ab ON 1 WHERE NULL
+        SELECT * FROM xy LEFT JOIN ab ON true WHERE NULL
     ]], {
         -- <join5-2.11>
         
diff --git a/test/sql-tap/limit.test.lua b/test/sql-tap/limit.test.lua
index 632c63402..9b728d8e0 100755
--- a/test/sql-tap/limit.test.lua
+++ b/test/sql-tap/limit.test.lua
@@ -328,7 +328,7 @@ test:do_execsql_test(
     "limit-5.3",
     [[
         DELETE FROM t5;
-        INSERT INTO t5 SELECT id, x-y, x+y FROM t1 WHERE x ORDER BY x DESC LIMIT 31;
+        INSERT INTO t5 SELECT id, x-y, x+y FROM t1 WHERE x <> 0 ORDER BY x DESC LIMIT 31;
         SELECT x, y FROM t5 ORDER BY x LIMIT 2;
     ]], {
         -- <limit-5.3>
diff --git a/test/sql-tap/resolver01.test.lua b/test/sql-tap/resolver01.test.lua
index aa383b34b..9fcf0c7c0 100755
--- a/test/sql-tap/resolver01.test.lua
+++ b/test/sql-tap/resolver01.test.lua
@@ -347,7 +347,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "resolver01-7.1",
     [[
-        SELECT 2 AS x WHERE (SELECT x AS y WHERE 3>y);
+        SELECT 2 AS x WHERE (SELECT x AS y WHERE 3>y) <> 0;
     ]], {
         -- <resolver01-7.1>
         2
diff --git a/test/sql-tap/select2.test.lua b/test/sql-tap/select2.test.lua
index c7f1e5d86..7f0188d6f 100755
--- a/test/sql-tap/select2.test.lua
+++ b/test/sql-tap/select2.test.lua
@@ -212,7 +212,7 @@ test:do_execsql_test(
     "select2-4.2",
     [[
         INSERT INTO bb VALUES(0);
-        SELECT * FROM aa CROSS JOIN bb WHERE b;
+        SELECT * FROM aa CROSS JOIN bb WHERE b <> 0;
     ]], {
         -- <select2-4.2>
         1, 2, 1, 4, 3, 2, 3, 4
@@ -232,7 +232,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select2-4.4",
     [[
-        SELECT * FROM aa, bb WHERE min(a,b);
+        SELECT * FROM aa, bb WHERE min(a,b) <> 0;
     ]], {
         -- <select2-4.4>
         1, 2, 1, 4, 3, 2, 3, 4
@@ -252,7 +252,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select2-4.6",
     [[
-        SELECT * FROM aa, bb WHERE CASE WHEN a=b-1 THEN 1 END;
+        SELECT * FROM aa, bb WHERE CASE WHEN a=b-1 THEN true END;
     ]], {
         -- <select2-4.6>
         1, 2, 3, 4
@@ -262,7 +262,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select2-4.7",
     [[
-        SELECT * FROM aa, bb WHERE CASE WHEN a=b-1 THEN 0 ELSE 1 END;
+        SELECT * FROM aa, bb WHERE CASE WHEN a=b-1 THEN false ELSE true END;
     ]], {
         -- <select2-4.7>
         1, 0, 1, 4, 3, 0, 3, 2
diff --git a/test/sql-tap/select6.test.lua b/test/sql-tap/select6.test.lua
index 7f6cc7939..c9960dc29 100755
--- a/test/sql-tap/select6.test.lua
+++ b/test/sql-tap/select6.test.lua
@@ -632,7 +632,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select6-7.3",
     [[
-        SELECT c,b,a,* FROM (SELECT 1 AS a, 2 AS b, 'abc' AS c WHERE 0)
+        SELECT c,b,a,* FROM (SELECT 1 AS a, 2 AS b, 'abc' AS c WHERE false)
     ]], {
         -- <select6-7.3>
         
@@ -642,7 +642,7 @@ test:do_execsql_test(
 test:do_execsql2_test(
     "select6-7.4",
     [[
-        SELECT c,b,a,* FROM (SELECT 1 AS a, 2 AS b, 'abc' AS c WHERE 1)
+        SELECT c,b,a,* FROM (SELECT 1 AS a, 2 AS b, 'abc' AS c WHERE true)
     ]], {
         -- <select6-7.4>
         "C", "abc", "B", 2, "A", 1, "A", 1, "B", 2, "C", "abc"
diff --git a/test/sql-tap/select9.test.lua b/test/sql-tap/select9.test.lua
index a54010f89..1ae16a658 100755
--- a/test/sql-tap/select9.test.lua
+++ b/test/sql-tap/select9.test.lua
@@ -541,7 +541,7 @@ test:do_execsql_test(
         CREATE TABLE t62(b INT primary key);
         INSERT INTO t61 VALUES(111);
         INSERT INTO t62 VALUES(222);
-        SELECT a FROM t61 WHERE 0 UNION SELECT b FROM t62;
+        SELECT a FROM t61 WHERE false UNION SELECT b FROM t62;
     ]], {
         -- <select9-6.1>
         222
@@ -551,7 +551,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select9-6.2",
     [[
-        SELECT a FROM t61 WHERE 0 UNION ALL SELECT b FROM t62;
+        SELECT a FROM t61 WHERE false UNION ALL SELECT b FROM t62;
     ]], {
         -- <select9-6.2>
         222
@@ -561,7 +561,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select9-6.3",
     [[
-        SELECT a FROM t61 UNION SELECT b FROM t62 WHERE 0;
+        SELECT a FROM t61 UNION SELECT b FROM t62 WHERE false;
     ]], {
         -- <select9-6.3>
         111
diff --git a/test/sql-tap/subquery.test.lua b/test/sql-tap/subquery.test.lua
index 1532ee3d8..7a3e270dc 100755
--- a/test/sql-tap/subquery.test.lua
+++ b/test/sql-tap/subquery.test.lua
@@ -918,8 +918,8 @@ test:do_execsql_test(
 test:do_execsql_test(
     "subquery-8.1",
     [[
-        SELECT (SELECT 0 FROM (SELECT * FROM t1)) AS x WHERE x;
-        SELECT (SELECT 0 FROM (SELECT * FROM (SELECT 0))) AS x WHERE x;
+        SELECT (SELECT 0 FROM (SELECT * FROM t1)) AS x WHERE x <> 0;
+        SELECT (SELECT 0 FROM (SELECT * FROM (SELECT 0))) AS x WHERE x <> 0;
     ]], {
         -- <subquery-8.1>
         
diff --git a/test/sql-tap/tkt2832.test.lua b/test/sql-tap/tkt2832.test.lua
index d3f24b586..cd75cc507 100755
--- a/test/sql-tap/tkt2832.test.lua
+++ b/test/sql-tap/tkt2832.test.lua
@@ -90,7 +90,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "tkt2832-3.2",
     [[
-        DELETE FROM t3 WHERE 1
+        DELETE FROM t3 WHERE true
     ]], {
         -- <tkt2832-3.2>
 
-- 
2.15.1

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

* [tarantool-patches] Re: [PATCH 2/9] sql: disallow text values participate in sum() aggregate
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 2/9] sql: disallow text values participate in sum() aggregate Nikita Pettik
@ 2019-04-16 14:12   ` Vladislav Shpilevoy
  2019-04-18 17:54     ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-16 14:12 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik; +Cc: kostja

Hi! Thanks for the patch! See 3 comments below.

On 14/04/2019 18:04, Nikita Pettik wrote:
> It is obvious that we can't add string with number except the case when
> string is a number literal in quotes (aka '123' or '0.5'). Before this
> patch values which can't be converted to numbers were just skipped.
> Now error is raised. Another one misbehavior was in using
> sql_value_numeric_type() function, which set flag indicating number
> value in memory cell, but didn't clear MEM_Str flag.

1. Before this concrete commit sql_value_numeric_type() worked correctly -
it used your refactored mem_apply_numeric_type(), which clears the old
type flags.

> As a result, we
> couldn't determine type of value in such memory cell without
> ambigiousness.

2. Can't find this word in a dictionary. Probably, ambiguousness?

> ---
>  src/box/sql/func.c            | 38 ++++++++++++++++++++++++--------------
>  src/box/sql/sqlInt.h          |  3 ---
>  src/box/sql/vdbe.c            | 19 ++-----------------
>  src/box/sql/vdbeInt.h         |  3 +--
>  test/sql-tap/select1.test.lua |  4 ++--
>  test/sql-tap/select5.test.lua |  3 ++-
>  6 files changed, 31 insertions(+), 39 deletions(-)
>> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> index 8cd00d43a..ec9123a66 100644
> --- a/src/box/sql/vdbeInt.h
> +++ b/src/box/sql/vdbeInt.h
> @@ -274,8 +274,7 @@ mem_type_to_str(const struct Mem *p);
>   * if we can do so without loss of information. Firstly, value
>   * is attempted to be converted to integer, and in case of fail -
>   * to floating point number. Note that function is assumed to be
> - * called only on memory cell containing string,
> - * i.e. memory->type == MEM_Str.
> + * called on memory cell containing string, i.e. mem->type == MEM_Str.

3. Please, move to the previous commit.

>   *
>   * @param record Memory cell containing value to be converted.
>   * @retval 0 If value can be converted to integer or number.

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

* [tarantool-patches] Re: [PATCH 9/9] sql: make <search condition> accept only boolean
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 9/9] sql: make <search condition> accept only boolean Nikita Pettik
@ 2019-04-16 14:12   ` Vladislav Shpilevoy
  2019-04-18 17:55     ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-16 14:12 UTC (permalink / raw)
  To: Nikita Pettik, tarantool-patches; +Cc: kostja

Thanks for the patch! See 4 comments below.

On 14/04/2019 18:04, Nikita Pettik wrote:
> <search condition> is a predicate used as a part of WHERE and
> JOIN clauses. ANSI SQL states that <search condition> must
> accept only boolean arguments. In our SQL it is implemented as
> bytecode instruction OP_If which in turn carries out logic of
> conditional jump. Since it can be involved in executing other routines
> different from <search condition>, 

1. Which other routines? What is a valid case of OP_If with non-boolean
value in check?

> we pass to it additional argument
> when generating bytecode for WHERE and JOIN clauses. When VDBE performs
> OP_If and detects such flag, it checks passed argument to be boolean.
> 
> Closes #3723

2. In addition, it fixes https://github.com/tarantool/tarantool/issues/3651,
doesn't it?

> diff --git a/test/sql-tap/e_delete.test.lua b/test/sql-tap/e_delete.test.lua
> index a58dc87c7..374a7d3e4 100755
> --- a/test/sql-tap/e_delete.test.lua
> +++ b/test/sql-tap/e_delete.test.lua
> @@ -89,15 +89,15 @@ test:do_delete_tests("e_delete-1.1", {
>  -- NULL are retained.
>  --
>  test:do_delete_tests("e_delete-1.2", {
> -    {1, "DELETE FROM t3 WHERE 1       ; SELECT x FROM t3", {}},
> -    {2, "DELETE FROM t4 WHERE 0  ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
> -    {3, "DELETE FROM t4 WHERE 0.0     ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
> +    {1, "DELETE FROM t3 WHERE true       ; SELECT x FROM t3", {}},
> +    {2, "DELETE FROM t4 WHERE false  ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
> +    {3, "DELETE FROM t4 WHERE false    ; SELECT x FROM t4", {1, 2, 3, 4, 5}},

3. The last two lines are exactly the same. Why not to drop one?

>      {4, "DELETE FROM t4 WHERE NULL    ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
>      {5, "DELETE FROM t4 WHERE y!='two'; SELECT x FROM t4", {2}},
>      {6, "DELETE FROM t4 WHERE y='two' ; SELECT x FROM t4", {}},
>      {7, "DELETE FROM t5 WHERE x=(SELECT max(x) FROM t5);SELECT x FROM t5", {1, 2, 3, 4}},
>      {8, "DELETE FROM t5 WHERE (SELECT max(x) FROM t4)  ;SELECT x FROM t5", {1, 2, 3, 4}},
> -    {9, "DELETE FROM t5 WHERE (SELECT max(x) FROM t6)  ;SELECT x FROM t5", {}},
> +    {9, "DELETE FROM t5 WHERE (SELECT max(x) FROM t6) != 0  ;SELECT x FROM t5", {}},
>      {10, "DELETE FROM t6 WHERE y>'seven' ; SELECT y FROM t6", {"one", "four", "five"}},
>  })
>  
> diff --git a/test/sql-tap/e_select1.test.lua b/test/sql-tap/e_select1.test.lua
> index 970eeeed9..e47b0f43d 100755
> --- a/test/sql-tap/e_select1.test.lua
> +++ b/test/sql-tap/e_select1.test.lua
> @@ -448,15 +448,15 @@ test:do_select_tests(
>  -- true are included from the dataset.
>  --
>  local data ={
> -    {"1"," SELECT * FROM t1 JOIN_PATTERN t2 ON (1) ",t1_cross_t2},
> -    {"2"," SELECT * FROM t1 JOIN_PATTERN t2 ON (0) ",{}},
> +    {"1"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
> +    {"2"," SELECT * FROM t1 JOIN_PATTERN t2 ON (false) ",{}},
>      {"3"," SELECT * FROM t1 JOIN_PATTERN t2 ON (NULL) ",{}},
> -    {"6"," SELECT * FROM t1 JOIN_PATTERN t2 ON (0.9) ",t1_cross_t2},
> -    {"7"," SELECT * FROM t1 JOIN_PATTERN t2 ON ('0.9') ",t1_cross_t2},
> -    {"8"," SELECT * FROM t1 JOIN_PATTERN t2 ON (0.0) ",{}},
> +    {"6"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
> +    {"7"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
> +    {"8"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},

4. The same. 3 duplicates.

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

* [tarantool-patches] Re: [PATCH 4/9] sql: introduce type boolean
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 4/9] sql: introduce type boolean Nikita Pettik
@ 2019-04-16 14:12   ` Vladislav Shpilevoy
  2019-04-18 17:54     ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-16 14:12 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik; +Cc: kostja

Thanks for the patch! See 17 comments below.

On 14/04/2019 18:04, Nikita Pettik wrote:
> This patch introduces basic facilities to operate on boolean type:
> boolean literals "true" and "false" where true > false; alias to null -
> unknown; column type "BOOLEAN" and shortcut "BOOL"; opportunity to
> insert and select boolean values from table; OR and AND predicates
> accept boolean arguments; CAST operation involving boolean type;
> comparison between boolean values (including VDBE sorter routines).
> 
> Part of #3648
> ---
>  extra/mkkeywordhash.c        |   5 +
>  src/box/execute.c            |   9 +
>  src/box/lua/lua_sql.c        |   3 +
>  src/box/sql/build.c          |  11 +-
>  src/box/sql/expr.c           |  14 +
>  src/box/sql/func.c           |  15 ++
>  src/box/sql/parse.y          |  15 ++
>  src/box/sql/sqlInt.h         |   6 +
>  src/box/sql/vdbe.c           |  34 ++-
>  src/box/sql/vdbeInt.h        |   6 +-
>  src/box/sql/vdbeapi.c        |  16 ++
>  src/box/sql/vdbeaux.c        |  31 ++-
>  src/box/sql/vdbemem.c        |  73 +++++-
>  test/sql-tap/whereG.test.lua |   6 +-
>  test/sql/types.result        | 601 ++++++++++++++++++++++++++++++++++++++++++-
>  test/sql/types.test.lua      | 132 ++++++++++
>  16 files changed, 952 insertions(+), 25 deletions(-)
> 
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index 7724e9415..c6fbb1af6 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -958,8 +955,7 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id
>  	sqlVdbeAddOp2(v, OP_IntCopy, reg_seq_id, first_col + 2);
>  
>  	/* 3. True, which is 1 in SQL  */

1. I guess, it is not '1' anymore.

> -	sqlVdbeAddOp2(v, OP_Bool, 0, first_col + 3);
> -	sqlVdbeChangeP4(v, -1, (char*)&const_true, P4_BOOL);
> +	sqlVdbeAddOp2(v, OP_Bool, true, first_col + 3);
>  
>  	sqlVdbeAddOp3(v, OP_MakeRecord, first_col + 1, 3, first_col);
>  
> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index 4b98bd175..6b38e8e66 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -3360,6 +3360,15 @@ expr_code_int(struct Parse *parse, struct Expr *expr, bool is_neg,
>  	}
>  }
>  

2. It is worth adding a comment. At a first glance I did not
understand what is 'mem', and what 'expr' is expected and allowed
to store - str, int, float?

> +static void
> +vdbe_emit_bool(struct Vdbe *v, const struct Expr *expr, int mem)
> +{
> +	const char *z = expr->u.zToken;

3. It would be safer to add an assertion here that expr actually
stores a string only.

4. The function is quite small, and is called in one place
only. Mark it 'inline'.

> +	assert(z != NULL);
> +	bool val = strncasecmp(z, "TRUE", 4) == 0;
> +	sqlVdbeAddOp2(v, OP_Bool, val, mem);
> +}
> +
>  /*
>   * Erase column-cache entry number i
>   */
> @@ -3764,6 +3773,11 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
>  			expr_code_int(pParse, pExpr, false, target);
>  			return target;
>  		}
> +	case TK_TRUE:
> +	case TK_FALSE: {
> +			vdbe_emit_bool(pParse->pVdbe, pExpr, target);

5. Correct me, if I am wrong but there is how I understand
boolean value emission.

 - Parser scans a literal. It marks 'true' with TK_TRUE, 'false'
   with TK_FALSE (taking into account case-insensitivity);

 - Here you merge TK_FALSE and TK_TRUE into a single 'case'
   statement;

 - Inside that statement you call vdbe_emit_bool, which again
   parses the string for being equal 'true' of 'false'.

So why? Since the first step you already have the boolean value.
The value is '== TK_TRUE'. You do not need to use strcasecmp here.
Just split these 'case' statements, and make vdbe_emit_bool()
taking a boolean value instead of expr.

6. I see, that all other 'case's set pExpr->type. Why don't you
do it here?

> +			return target;
> +		}
>  #ifndef SQL_OMIT_FLOATING_POINT
>  	case TK_FLOAT:{
>  			pExpr->type = FIELD_TYPE_INTEGER;
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 3cdb119c8..860cd8920 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -1091,6 +1101,11 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
>  			}
>  			break;
>  		}
> +	case MP_BOOL: {
> +		sql_result_text(context, argv[0]->u.b ? "true" : "false", -1,
> +				SQL_TRANSIENT);
> +		break;
> +	}
>  	default:{
>  			assert(sql_value_mp_type(argv[0]) == MP_NIL);
>  			sql_result_text(context, "NULL", 4, SQL_STATIC);

7. In the same file lengthFunc() now accepts boolean value. But obviously
it should not, bool is not string. It exacerbates 4159 issue.

> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 9134f767d..4f62c2782 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -2161,6 +2167,24 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
>  			}
>  			break;
>  		}
> +	} else if ((flags1 | flags3) & MEM_Bool) {
> +		/*
> +		 * If one of values is of type BOOLEAN, then the
> +		 * second one must be BOOLEAN as well. Otherwise
> +		 * an error is raised.
> +		 */
> +		bool is_bool_type_arg1 = flags1 & MEM_Bool;
> +		bool is_bool_type_arg3 = flags3 & MEM_Bool;
> +		if (! is_bool_type_arg1 || ! is_bool_type_arg3) {
> +			char *inconsistent_type = ! is_bool_type_arg1 ?
> +						  mem_type_to_str(pIn1) :
> +						  mem_type_to_str(pIn3);
> +			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
> +				 inconsistent_type, "boolean");
> +			rc = SQL_TARANTOOL_ERROR;
> +			goto abort_due_to_error;
> +		}
> +		res = sqlMemCompare(pIn3, pIn1, NULL);
>  	} else {

8. Looking at all these type comparison prohibitions I am getting
afraid of how will we select from SCALAR columns?

A schema:

    box.execute('CREATE TABLE t (id INTEGER PRIMARY KEY, a SCALAR UNIQUE);')
    box.execute("INSERT INTO t VALUES (1, 1), (2, true), (3, false), (4, 'str')")

SQL select:

    > box.execute('SELECT * FROM t WHERE a > 1')
    ---
    - error: 'Type mismatch: can not convert INTEGER to boolean'
    ...

The same Lua select:

    > box.space.T.index[1]:select({1}, {iterator = 'GT'})
    ---
    - - [4, 'str']
    ...

In fact, now we can not use SCALAR in SQL for any comparisons
because it will raise type mismatch on literally everything.

What are we going to do with it? IMO, we could add a flag like
'is_scalar' to struct Mem in its flags section, which would allow
to compare this value with anything. Looks crutchy though. And is
not a part of this issue of course, but should be filed into the
issue list.

I see a similar issue https://github.com/tarantool/tarantool/issues/4124.
Probably, it is worth extending it instead of opening new.

>  		enum field_type type = pOp->p5 & FIELD_TYPE_MASK;
>  		if (sql_type_is_numeric(type)) {
> @@ -2414,6 +2438,8 @@ case OP_Or: {             /* same as TK_OR, in1, in2, out3 */
>  	pIn1 = &aMem[pOp->p1];
>  	if (pIn1->flags & MEM_Null) {
>  		v1 = 2;
> +	} else if ((pIn1->flags & MEM_Bool) != 0) {
> +		v1 = pIn1->u.b;
>  	} else {
>  		int64_t i;
>  		if (sqlVdbeIntValue(pIn1, &i) != 0) {
> @@ -2427,6 +2453,8 @@ case OP_Or: {             /* same as TK_OR, in1, in2, out3 */
>  	pIn2 = &aMem[pOp->p2];
>  	if (pIn2->flags & MEM_Null) {
>  		v2 = 2;
> +	} else if ((pIn2->flags & MEM_Bool) != 0) {
> +		v2 = pIn2->u.b;
>  	} else {
>  		int64_t i;
>  		if (sqlVdbeIntValue(pIn2, &i) != 0) {
> @@ -2540,6 +2568,8 @@ case OP_IfNot: {            /* jump, in1 */
>  	pIn1 = &aMem[pOp->p1];
>  	if (pIn1->flags & MEM_Null) {
>  		c = pOp->p3;
> +	} else if ((pIn1->flags & MEM_Bool) != 0) {
> +		c = pOp->opcode==OP_IfNot ? ! pIn1->u.b : pIn1->u.b;
>  	} else {
>  		double v;
>  		if (sqlVdbeRealValue(pIn1, &v) != 0) {
> diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
> index 6e867ca84..e1302afc0 100644
> --- a/src/box/sql/vdbeapi.c
> +++ b/src/box/sql/vdbeapi.c
> @@ -982,6 +990,14 @@ sql_column_int(sql_stmt * pStmt, int i)
>  	return val;
>  }
>  
> +bool
> +sql_column_boolean(sql_stmt *stmt, int i)
> +{
> +	bool val = sql_value_boolean(columnMem(stmt, i));
> +	columnMallocFailure(stmt);
> +	return val;
> +}
> +
>  sql_int64
>  sql_column_int64(sql_stmt * pStmt, int i)
>  {

9. You did not update sql_bind_value(), now it binds NULL
on any boolean value. On the other hand it is not used at
all and probably could be removed.

> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index 0cc3c1487..0f56028e5 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -3383,6 +3383,17 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
>  		return (f2 & MEM_Null) - (f1 & MEM_Null);
>  	}
>  
> +	if (combined_flags & MEM_Bool) {

10. Sorry for nit, but we use explicit != 0 in conditions.

> +		if ((f1 & f2 & MEM_Bool) != 0) {
> +			if (pMem1->u.b == pMem2->u.b)
> +				return 0;
> +			if (pMem1->u.b)
> +				return 1;
> +			return -1;
> +		}
> +		return -1;
> +	}
> +
>  	/* At least one of the two values is a number
>  	 */
>  	if (combined_flags & (MEM_Int | MEM_Real)) {
> @@ -3561,10 +3572,16 @@ sqlVdbeCompareMsgpack(const char **key1,
>  			break;
>  		}
>  	case MP_BOOL:{
> -			assert((unsigned char)(*aKey1) == 0xc2
> -			       || (unsigned char)*aKey1 == 0xc3);
> -			mem1.u.i = (unsigned)(size_t) aKey1++ - 0xc2;
> -			goto do_int;
> +

11. Stray empty line.

> +			mem1.u.b = mp_decode_bool(&aKey1);
> +			if ((pKey2->flags & MEM_Bool) != 0) {
> +				if (mem1.u.b != pKey2->u.b) {
> +					rc = mem1.u.b ? 1 : -1;
> +				}

12. Usually we omit curly braces when body of a cycle or a condition
is one line.

> +			} else {
> +				rc = (pKey2->flags & MEM_Null) != 0 ? +1 : -1;

13. '+1' ? Sorry, we do not use prefix '+'.

> +			}
> +			break;
>  		}
>  	case MP_UINT:{
>  			uint64_t v = mp_decode_uint(&aKey1);
> diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
> index 15a2f55cb..4fed0eefe 100644
> --- a/src/box/sql/vdbemem.c
> +++ b/src/box/sql/vdbemem.c
> @@ -499,6 +501,16 @@ sqlVdbeRealValue(Mem * pMem, double *v)
>  	return -1;
>  }
>  
> +int
> +vdbe_value_boolean(Mem *mem, bool *b)
> +{
> +	if (mem->flags  & MEM_Bool) {
> +		*b = mem->u.b;
> +		return 0;
> +	}
> +	return -1;
> +}

14. Probably it is better to name it by Mem, not by
Vdbe, because it does not take a Vdbe pointer. And
similar functions contract type names: 'boolean' -> 'bool',
'integer' -> 'int', etc. In summary, 'mem_value_bool'
for example.

> +
>  /*
>   * The MEM structure is already a MEM_Real.  Try to also make it a
>   * MEM_Int if we can.
> @@ -594,6 +606,37 @@ sqlVdbeMemNumerify(Mem * pMem)
>  	return SQL_OK;
>  }
>  
> +/**
> + * According to ANSI SQL string value can be converted to boolean
> + * type if string consists of literal "true" or "false" and
> + * number of leading spaces.
> + *
> + * For instance, "   tRuE" can be successfully converted to
> + * boolean value true.
15. So '   true' is valid, but 'true    ' is not? As I see the
standard, it is not so. Cite:

"""
    If TD is boolean, then

    Case:

        a) If SD is character string, then SV is replaced by

            TRIM ( BOTH ' ' FROM VE )

           Case:

            i)  If the rules for <literal> in Subclause 5.3, “<literal>”,
                can be applied to SV to determine a valid value of the
                data type TD, then let TV be that value.

            ii) Otherwise, an exception condition is raised: data
                exception — invalid character value for cast.

        b) If SD is boolean, then TV is SV.
"""

TD - cast type,
SD - type of cast target,
VE,SV - cast target,
TV - cast result value.

The key line: "TRIM ( BOTH ' ' FROM VE )" -> 'BOTH'.
Spaces are trimmed off from both sides.

> + *
> + * @param str String to be converted to boolean.
> + *            Assumed to be null terminated.
> + * @param result Resulting value of cast.
> + * @retval 0 If string satisfies conditions above.
> + * @retval -1 Otherwise.
> + */
> +static int
> +str_cast_to_boolean(const char *str, bool *result)
> +{
> +	assert(str != NULL);
> +	for (; *str == ' '; str++);
> +	size_t rest_str_len = strlen(str);
> +	if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
> +		*result = true;
> +		return 0;
> +	}
> +	if (rest_str_len == 5 && strncasecmp(str, "false", 5) == 0) {
> +		*result = false;
> +		return 0;
> +	}

16. I guess it is far from the most efficient implementation. You
calculate length of the whole string before comparison, scanning
its twice in the worst and most common case. Please, consider my
fixes below. I did not test them though, so probably there are some
typos.

Note, that in my fixes I've already accounted my comment about
trimming trailing whitespaces.

============================================================================
@@ -625,16 +625,20 @@ str_cast_to_boolean(const char *str, bool *result)
 {
 	assert(str != NULL);
 	for (; *str == ' '; str++);
-	size_t rest_str_len = strlen(str);
-	if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
+	if (strncasecmp(str, "true", 4) == 0) {
+		str += 4;
 		*result = true;
-		return 0;
-	}
-	if (rest_str_len == 5 && strncasecmp(str, "false", 5) == 0) {
+	} else if (strncasecmp(str, "false", 5) == 0) {
+		str += 5;
 		*result = false;
-		return 0;
+	} else {
+		return -1;
 	}
-	return -1;
+	for (; *str != 0; ++str) {
+		if (*str != ' ')
+			return -1;
+	}
+	return 0;
 }
============================================================================

> +	return -1;
> +}
> +
>  /*
>   * Cast the datatype of the value in pMem according to the type
>   * @type.  Casting is different from applying type in that a cast
> diff --git a/test/sql/types.result b/test/sql/types.result
> index 0a5085658..3aa0169e2 100644
> --- a/test/sql/types.result
> +++ b/test/sql/types.result
> +box.execute("SELECT unknown;")
> +---
> +- metadata:
> +  - name: unknown
> +    type: scalar

17. Should not it be boolean?

> +  rows:
> +  - [null]
> +...

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

* [tarantool-patches] Re: [PATCH 3/9] sql: use msgpack types instead of custom ones
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 3/9] sql: use msgpack types instead of custom ones Nikita Pettik
@ 2019-04-16 14:12   ` Vladislav Shpilevoy
  2019-04-18 17:54     ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-16 14:12 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik; +Cc: kostja

Thanks for the patch! See 4 comments below.

On 14/04/2019 18:04, Nikita Pettik wrote:
> This patch provides straightforward refactoring replacing enum sql_type
> with enum mp_type. Note that we use msgpack format instead of field_type
> since it is more suitable when dealing with NULLs.
> ---
>  src/box/bind.c          |  37 ++++++------------
>  src/box/execute.c       |  12 +++---
>  src/box/lua/execute.c   |   7 +---
>  src/box/lua/lua_sql.c   |  12 +++---
>  src/box/sql/date.c      |   2 +-
>  src/box/sql/func.c      | 100 ++++++++++++++++++++++++------------------------
>  src/box/sql/legacy.c    |   2 +-
>  src/box/sql/sqlInt.h    |  16 ++------
>  src/box/sql/vdbeapi.c   |  66 ++++++++++----------------------
>  src/box/sql/whereexpr.c |   2 +-
>  10 files changed, 102 insertions(+), 154 deletions(-)
> 
> diff --git a/src/box/bind.c b/src/box/bind.c
> index 4b57e23c8..5aa79751a 100644
> --- a/src/box/bind.c
> +++ b/src/box/bind.c
> @@ -138,6 +121,7 @@ sql_bind_decode(struct sql_bind *bind, int i, const char **packet)
>  	default:
>  		unreachable();
>  	}
> +	bind->type = type;

1. struct sql_bind.type still is uint8_t and commented as
'SQL type of the value.'. Please, change its type to enum mp_type
and say in the comment 'MessagePack type of the value.'

>  	return 0;
>  }
>  > diff --git a/src/box/sql/date.c b/src/box/sql/date.c
> index 5f5272ea3..ead0c01d0 100644
> --- a/src/box/sql/date.c
> +++ b/src/box/sql/date.c
> @@ -937,7 +937,7 @@ isDate(sql_context * context, int argc, sql_value ** argv, DateTime * p)
>  	if (argc == 0) {
>  		return setDateTimeToCurrent(context, p);
>  	}
> -	if ((eType = sql_value_type(argv[0])) == SQL_FLOAT
> +	if ((eType = sql_value_type(argv[0])) == MP_DOUBLE

2. Firstly, the code is commented out. Secondly, sql_value_type
does not exist anymore. Thirdly, eType is of type int, not
enum mp_type, and can not be compared with MP_DOUBLE.

Please, rename sql_value_mp_type to sql_value_type, because we
do not have 'not mp' type anymore. Then this hunk will be ok
except the third objection.

>  	    || eType == SQL_INTEGER) {
>  		setRawDateNumber(p, sql_value_double(argv[0]));
>  	} else {
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 9adfeec67..3cdb119c8 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -87,10 +87,10 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
>  	pColl = sqlGetFuncCollSeq(context);
>  	assert(mask == -1 || mask == 0);
>  	iBest = 0;
> -	if (sql_value_type(argv[0]) == SQL_NULL)
> +	if (sql_value_mp_type(argv[0]) == MP_NIL)

3. Please, move that check into a separate static inline
function in a header file. It appeared to be used often.
A possible name 'sql_value_is_null'.

>  		return;
>  	for (i = 1; i < argc; i++) {
> -		if (sql_value_type(argv[i]) == SQL_NULL)
> +		if (sql_value_mp_type(argv[i]) == MP_NIL)
>  			return;
>  		if ((sqlMemCompare(argv[iBest], argv[i], pColl) ^ mask) >=
>  		    0) {
> @@ -139,15 +139,15 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
>  
>  	assert(argc == 1);
>  	UNUSED_PARAMETER(argc);
> -	switch (sql_value_type(argv[0])) {
> -	case SQL_BLOB:
> -	case SQL_INTEGER:
> -	case SQL_FLOAT:{
> +	switch (sql_value_mp_type(argv[0])) {
> +	case MP_BIN:
> +	case MP_INT:
> +	case MP_DOUBLE:{

4. Probably you could fix part of this:
https://github.com/tarantool/tarantool/issues/4159 in scope of
this commit, alongside.

>  			sql_result_int(context,
>  					   sql_value_bytes(argv[0]));
>  			break;
>  		}
> -	case SQL_TEXT:{
> +	case MP_STR:{
>  			const unsigned char *z = sql_value_text(argv[0]);
>  			if (z == 0)

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

* [tarantool-patches] Re: [PATCH 7/9] sql: make predicates accept and return boolean
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 7/9] sql: make predicates accept and " Nikita Pettik
@ 2019-04-16 14:12   ` Vladislav Shpilevoy
  2019-04-18 17:55     ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-16 14:12 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik; +Cc: kostja

Thanks for the patch! See 5 comments below.

On 14/04/2019 18:04, Nikita Pettik wrote:
> This patch make following predicates accept and return only values
> of type boolean: IN, EXISTS, OR, AND, NOT, BETWEEN, IS (NULL).
> In terms of approach, it is enough to patch opcodes implementing these
> predicates.
> 
> Part of #3723
> ---
>  src/box/sql/expr.c              | 26 ++++++++++++--------------
>  src/box/sql/parse.y             |  4 +++-
>  src/box/sql/parse_def.c         |  5 +++++
>  src/box/sql/select.c            |  2 +-
>  src/box/sql/sqlInt.h            |  1 +
>  src/box/sql/vdbe.c              | 37 ++++++++++++++-----------------------
>  test/sql-tap/cse.test.lua       |  6 +++---
>  test/sql-tap/e_select1.test.lua |  2 +-
>  test/sql-tap/in1.test.lua       |  6 +++---
>  test/sql-tap/misc3.test.lua     |  2 +-
>  test/sql-tap/select4.test.lua   |  2 +-
>  test/sql-tap/tkt3541.test.lua   |  2 +-
>  test/sql/types.result           | 22 +++++++++++++---------
>  13 files changed, 59 insertions(+), 58 deletions(-)
> 
> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index e4de472a8..c5ec55de2 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -1134,7 +1130,10 @@ sql_and_expr_new(struct sql *db, struct Expr *left_expr,
>  	} else if (exprAlwaysFalse(left_expr) || exprAlwaysFalse(right_expr)) {
>  		sql_expr_delete(db, left_expr, false);
>  		sql_expr_delete(db, right_expr, false);
> -		return sql_expr_new(db, TK_INTEGER, &sqlIntTokens[0]);
> +		struct Expr *f = sql_expr_new(db, TK_FALSE,
> +					      &sql_boolean_tokens[0]);
> +		f->type = FIELD_TYPE_BOOLEAN;

1. No check for f == NULL.

2. Why do you need f->type at all, if anyway you use sql_expr_type()
to get the type? Why isn't this field dropped still?

> +		return f;
>  	} else {
>  		struct Expr *new_expr = sql_expr_new_anon(db, TK_AND);
>  		sqlExprAttachSubtrees(db, new_expr, left_expr, right_expr);
> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
> index f64a84948..b520b23d5 100644
> --- a/src/box/sql/parse.y
> +++ b/src/box/sql/parse.y
> @@ -1237,11 +1237,13 @@ expr(A) ::= expr(A) in_op(N) LP exprlist(Y) RP(E). [IN] {
>      ** regardless of the value of expr1.
>      */
>      sql_expr_delete(pParse->db, A.pExpr, false);
> -    A.pExpr = sql_expr_new_dequoted(pParse->db, TK_INTEGER, &sqlIntTokens[N]);
> +    int tk = N == 0 ? TK_FALSE : TK_TRUE;
> +    A.pExpr = sql_expr_new_dequoted(pParse->db, tk, &sql_boolean_tokens[N]);

3. Why do you use 'dequoted'? Do you store not normalized names in
sql_boolean_tokens? If you do, then why not to store already
normalized names and use sql_expr_new() here so as to avoid ICU calls?

A normalized name is always uppercased, but sql_boolean_tokens stores
lowercase strings. I tried to do this:

    -    A.pExpr = sql_expr_new_dequoted(pParse->db, tk, &sql_boolean_tokens[N]);
    +    A.pExpr = sql_expr_new(pParse->db, tk, &sql_boolean_tokens[N]);

And the tests passed. Why?

Another question - why boolean constant needs zToken at all? As I understand,
the only reason is that sqlExprCodeTarget parses zToken again even having ready
at hand TK_FALSE and TK_TRUE values. If I am right, you can drop boolean_tokens
array once you've fixed my comment 5 from the main patch (4/9).

The same question could be asked for integer constants stored in struct Expr
and having zToken. The only reason for that is the delayed parsing of the tokens,
as I understand. First they all are stored in Expr.zToken, and then parsed in
CodeTarget. But it would not be necessary if we parsed them right after reading and
storing actual value into Expr.u.iValue. In such a case zToken would be used for
identifiers and strings only, not for constants which I think appear quite often.
The reasoning behind my proposal is that parsing of an integer is likely to be
faster itself than calling ICU normalization functions.

The same can be said about float/double values, but I do not see such a type in
Expr.u.

Of course, all these integer/float-related thoughts are not about this patchset.
Probably, it is worth opening a separate issue.

>      if (A.pExpr == NULL) {
>        pParse->is_aborted = true;
>        return;
>      }
> +    A.pExpr->type = FIELD_TYPE_BOOLEAN;
>    }else if( Y->nExpr==1 ){
>      /* Expressions of the form:
>      **
> diff --git a/test/sql-tap/cse.test.lua b/test/sql-tap/cse.test.lua
> index 4b25f936d..39c1cc4ca 100755
> --- a/test/sql-tap/cse.test.lua
> +++ b/test/sql-tap/cse.test.lua
> @@ -132,7 +132,7 @@ test:do_execsql_test(
>  test:do_execsql_test(
>      "cse-1.7",
>      [[
> -        SELECT a, -a, ~a, NOT a, NOT NOT a, a-a, a+a, a*a, a/a, a FROM t1
> +        SELECT a, -a, ~a, NOT CAST(a AS BOOLEAN), NOT NOT CAST(a AS BOOLEAN), a-a, a+a, a*a, a/a, a FROM t1
>      ]], {
>          -- <cse-1.7>
>          1, -1, -2, 0, 1, 0, 2, 1, 1, 1, 2, -2, -3, 0, 1, 0, 4, 4, 1, 2

4. Above the fourth selected column is 'NOT CAST(a AS BOOLEAN)'. It is either true or
false, but in the result set it is 0. Why? First I thought, that probably 0 == false
in Lua, but it is not as a test showed:

tarantool> 0 == false
---
- false
...

tarantool> 1 == true
---
- false
...

> diff --git a/test/sql-tap/tkt3541.test.lua b/test/sql-tap/tkt3541.test.lua
> index 08a9be557..05247728d 100755
> --- a/test/sql-tap/tkt3541.test.lua
> +++ b/test/sql-tap/tkt3541.test.lua
> @@ -39,7 +39,7 @@ test:do_test(
>      "tkt3541-1.2",
>      function()
>          return test:execsql [[
> -            SELECT CASE NOT max(x) WHEN min(x) THEN 1 ELSE max(x) END FROM t1;
> +            SELECT CASE max(x) = 0 WHEN min(x) > 0 THEN 1 ELSE max(x) END FROM t1;

5. If negative numbers are not treated as FALSE, then I
guess you meant 'min(x) <> 0'.

>          ]]
>      end, {
>          -- <tkt3541-1.2>

My total diff which I am not sure why does not break tests:

==============================================================================
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index c5ec55de2..42b60babd 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -1130,10 +1130,7 @@ sql_and_expr_new(struct sql *db, struct Expr *left_expr,
 	} else if (exprAlwaysFalse(left_expr) || exprAlwaysFalse(right_expr)) {
 		sql_expr_delete(db, left_expr, false);
 		sql_expr_delete(db, right_expr, false);
-		struct Expr *f = sql_expr_new(db, TK_FALSE,
-					      &sql_boolean_tokens[0]);
-		f->type = FIELD_TYPE_BOOLEAN;
-		return f;
+		return sql_expr_new_anon(db, TK_FALSE);
 	} else {
 		struct Expr *new_expr = sql_expr_new_anon(db, TK_AND);
 		sqlExprAttachSubtrees(db, new_expr, left_expr, right_expr);
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index b520b23d5..f75999c35 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1238,12 +1238,11 @@ expr(A) ::= expr(A) in_op(N) LP exprlist(Y) RP(E). [IN] {
     */
     sql_expr_delete(pParse->db, A.pExpr, false);
     int tk = N == 0 ? TK_FALSE : TK_TRUE;
-    A.pExpr = sql_expr_new_dequoted(pParse->db, tk, &sql_boolean_tokens[N]);
+    A.pExpr = sql_expr_new(pParse->db, tk, &sql_boolean_tokens[N]);
     if (A.pExpr == NULL) {
       pParse->is_aborted = true;
       return;
     }
-    A.pExpr->type = FIELD_TYPE_BOOLEAN;
   }else if( Y->nExpr==1 ){
     /* Expressions of the form:
     **

==============================================================================

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

* [tarantool-patches] Re: [PATCH 5/9] sql: improve type determination for column meta
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 5/9] sql: improve type determination for column meta Nikita Pettik
@ 2019-04-16 14:12   ` Vladislav Shpilevoy
  2019-04-18 17:54     ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-16 14:12 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik; +Cc: kostja



On 14/04/2019 18:04, Nikita Pettik wrote:
> In most cases we don't assign and store type of node of expression AST
> (except for constant literals).  To determine type of node  we use
> sql_expr_type() function, which implements logic of quite simple
> recursive tree traversal. Before this patch we set type of node after
> code generation in sqlExprCodeTarget() without any traversal. This
> approach is way worse even then sql_expr_type().

Why? How is recursive scan of the tree better than simple access to
a struct's attribute? I see, that your patch improves type determination
precision, but I do not understand how, and what a perf price for that?
If you are not sure about the price, we could ask Alexander to run
benches on your branch before pushing into the master.

> So, to improve accuracy
> of type determination, let's always call that method and remove type
> assignment in sqlExprCodeTarget().

This patch fixes the issue
https://github.com/tarantool/tarantool/issues/4126.

> ---
>  src/box/sql/expr.c                         | 19 ++++---------------
>  src/box/sql/select.c                       |  2 +-
>  test/sql/check-clear-ephemeral.result      |  2 +-
>  test/sql/gh-2347-max-int-literals.result   |  2 +-
>  test/sql/gh-3199-no-mem-leaks.result       | 18 +++++++++---------
>  test/sql/gh-3888-values-blob-assert.result |  2 +-
>  test/sql/persistency.result                |  8 ++++----
>  test/sql/transition.result                 |  4 ++--
>  test/sql/types.result                      | 10 +++++-----
>  9 files changed, 28 insertions(+), 39 deletions(-)
> 

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

* [tarantool-patches] Re: [PATCH 6/9] sql: make comparison predicate return boolean
  2019-04-14 15:04 ` [tarantool-patches] [PATCH 6/9] sql: make comparison predicate return boolean Nikita Pettik
@ 2019-04-16 14:12   ` Vladislav Shpilevoy
  2019-04-18 17:54     ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-16 14:12 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik; +Cc: kostja

Thanks for the patch! See 3 comments below.

On 14/04/2019 18:04, Nikita Pettik wrote:
> According to ANSI SQL result of comparison predicates must be BOOLEAN.
> Before introduction of BOOLEAN type they returned 0 and 1. Now we can
> change those values to false and true respectively.
> 
> Part of #3723
> ---
>  src/box/sql/expr.c                    |  1 +
>  src/box/sql/vdbe.c                    |  4 ++--
>  test/sql-tap/aggnested.test.lua       | 10 +++++-----
>  test/sql-tap/identifier_case.test.lua | 10 +++++-----
>  test/sql-tap/tkt3346.test.lua         |  2 +-
>  test/sql/types.result                 | 20 ++++++++++----------
>  6 files changed, 24 insertions(+), 23 deletions(-)
> 
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 10794b18e..43d4262e5 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -2273,8 +2273,8 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
>  			if ((pOp->opcode==OP_Eq)==res2) break;
>  		}
>  		memAboutToChange(p, pOut);
> -		MemSetTypeFlag(pOut, MEM_Int);
> -		pOut->u.i = res2;
> +		MemSetTypeFlag(pOut, MEM_Bool);
> +		pOut->u.b = res2;

1. Please, update the comments about SQL_STOREP2 and SQL_KEEPNULL.
They still say the result is stored as int 1/0.

>  		REGISTER_TRACE(pOp->p2, pOut);
>  	} else {
>  		VdbeBranchTaken(res!=0, (pOp->p5 & SQL_NULLEQ)?2:3);
> diff --git a/test/sql-tap/aggnested.test.lua b/test/sql-tap/aggnested.test.lua
> index 656576b70..67a9ba891 100755
> --- a/test/sql-tap/aggnested.test.lua
> +++ b/test/sql-tap/aggnested.test.lua
> @@ -215,7 +215,7 @@ test:do_execsql_test("aggnested-3.2-2",
>      ]],
>      {
>          -- <aggnested-3.2>
> -        0
> +        ""

2. Why? There 'sum' was selected. It can not return text.

>          -- </aggnested-3.2>
>      })
>  
> @@ -227,13 +227,13 @@ test:do_execsql_test("aggnested-3.3",
>          INSERT INTO t1 VALUES(4469,2),(4469,1);
>          CREATE TABLE t2 (value2 INT PRIMARY KEY);
>          INSERT INTO t2 VALUES(1);
> -        SELECT (SELECT sum(value2=value1) FROM t2), max(value1)
> +        SELECT (SELECT sum(value1) FROM t2 where value1=value2), max(value1)
>            FROM t1
>           GROUP BY id1;
>      ]],
>      {
>          -- <aggnested-3.3>
> -        0, 2
> +        "", 2

3. The same.

>          -- </aggnested-3.3>
>      })
>  

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

* [tarantool-patches] Re: [PATCH 8/9] sql: make LIKE predicate return boolean result
       [not found] ` <b2a84f129c2343d3da3311469cbb7b20488a21c2.1555252410.git.korablev@tarantool.org>
@ 2019-04-16 14:12   ` Vladislav Shpilevoy
  2019-04-18 17:55     ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-16 14:12 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik; +Cc: kostja



On 14/04/2019 18:04, Nikita Pettik wrote:
> According to ANSI, LIKE predicate should return boolean result.
> This patch changes type of return value of LIKE predicate.
> 
> Part of #3723
> ---
>  src/box/sql/func.c                                 | 12 ++--
>  src/box/sql/sqlInt.h                               |  3 +
>  src/box/sql/vdbeInt.h                              |  4 ++
>  src/box/sql/vdbeapi.c                              |  6 ++
>  src/box/sql/vdbemem.c                              |  8 +++
>  .../gh-3251-string-pattern-comparison.test.lua     | 76 +++++++++++-----------
>  6 files changed, 65 insertions(+), 44 deletions(-)
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 860cd8920..baa739ba4 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -974,7 +974,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
>  		sql_result_error(context, err_msg, -1);
>  		return;
>  	}
> -	sql_result_int(context, res == MATCH);
> +	sql_result_boolean(context, res == MATCH);

Probably we should either contract all the names,
or do not contract any. Here the name was contracted: 'int',
not 'integer'. Then it should be 'bool', not 'boolean'.

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

* [tarantool-patches] Re: [PATCH 2/9] sql: disallow text values participate in sum() aggregate
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-04-18 17:54     ` n.pettik
  2019-04-22 18:02       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 42+ messages in thread
From: n.pettik @ 2019-04-18 17:54 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy



> On 16 Apr 2019, at 17:12, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Hi! Thanks for the patch! See 3 comments below.
> 
> On 14/04/2019 18:04, Nikita Pettik wrote:
>> It is obvious that we can't add string with number except the case when
>> string is a number literal in quotes (aka '123' or '0.5'). Before this
>> patch values which can't be converted to numbers were just skipped.
>> Now error is raised. Another one misbehavior was in using
>> sql_value_numeric_type() function, which set flag indicating number
>> value in memory cell, but didn't clear MEM_Str flag.
> 
> 1. Before this concrete commit sql_value_numeric_type() worked correctly -
> it used your refactored mem_apply_numeric_type(), which clears the old
> type flags.

Ok, indeed. Fixed commit message to the next:

    sql: disallow text values participate in sum() aggregate
    
    It is obvious that we can't add string with number except the case when
    string is a number literal in quotes (aka '123' or '0.5'). Before this
    patch values which can't be converted to numbers were just skipped.
    Now error is raised. This patch also removes sql_value_numeric_type()
    function since it is not used anymore: instead we invoke
    mem_apply_numeric_type().


>> As a result, we
>> couldn't determine type of value in such memory cell without
>> ambigiousness.
> 
> 2. Can't find this word in a dictionary. Probably, ambiguousness?

Removed this mention at all. See above.

>> index 8cd00d43a..ec9123a66 100644
>> --- a/src/box/sql/vdbeInt.h
>> +++ b/src/box/sql/vdbeInt.h
>> @@ -274,8 +274,7 @@ mem_type_to_str(const struct Mem *p);
>>  * if we can do so without loss of information. Firstly, value
>>  * is attempted to be converted to integer, and in case of fail -
>>  * to floating point number. Note that function is assumed to be
>> - * called only on memory cell containing string,
>> - * i.e. memory->type == MEM_Str.
>> + * called on memory cell containing string, i.e. mem->type == MEM_Str.
> 
> 3. Please, move to the previous commit.

Moved.

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

* [tarantool-patches] Re: [PATCH 3/9] sql: use msgpack types instead of custom ones
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-04-18 17:54     ` n.pettik
  2019-04-22 18:02       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 42+ messages in thread
From: n.pettik @ 2019-04-18 17:54 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>> diff --git a/src/box/bind.c b/src/box/bind.c
>> index 4b57e23c8..5aa79751a 100644
>> --- a/src/box/bind.c
>> +++ b/src/box/bind.c
>> @@ -138,6 +121,7 @@ sql_bind_decode(struct sql_bind *bind, int i, const char **packet)
>> 	default:
>> 		unreachable();
>> 	}
>> +	bind->type = type;
> 
> 1. struct sql_bind.type still is uint8_t and commented as
> 'SQL type of the value.'. Please, change its type to enum mp_type
> and say in the comment 'MessagePack type of the value.’

diff --git a/src/box/bind.h b/src/box/bind.h
index cacb4a2d9..a915381e4 100644
--- a/src/box/bind.h
+++ b/src/box/bind.h
@@ -39,6 +39,8 @@ extern "C" {
 #include <stdint.h>
 #include <stdlib.h>
 
+#include "msgpuck.h"
+
 struct sql_stmt;
 
 /**
@@ -55,8 +57,8 @@ struct sql_bind {
 
        /** Byte length of the value. */
        uint32_t bytes;
-       /** SQL type of the value. */
-       uint8_t type;
+       /** MessagePack type of the value. */
+       enum mp_type type;
        /** Bind value. */
        union {
                double d;

>>> diff --git a/src/box/sql/date.c b/src/box/sql/date.c
>> index 5f5272ea3..ead0c01d0 100644
>> --- a/src/box/sql/date.c
>> +++ b/src/box/sql/date.c
>> @@ -937,7 +937,7 @@ isDate(sql_context * context, int argc, sql_value ** argv, DateTime * p)
>> 	if (argc == 0) {
>> 		return setDateTimeToCurrent(context, p);
>> 	}
>> -	if ((eType = sql_value_type(argv[0])) == SQL_FLOAT
>> +	if ((eType = sql_value_type(argv[0])) == MP_DOUBLE
> 
> 2. Firstly, the code is commented out. Secondly, sql_value_type
> does not exist anymore. Thirdly, eType is of type int, not
> enum mp_type, and can not be compared with MP_DOUBLE.
> 
> Please, rename sql_value_mp_type to sql_value_type, because we
> do not have 'not mp' type anymore. Then this hunk will be ok
> except the third objection.

Ok, renamed and fixed type of eType (nevertheless that chunk is
related to dead code).

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 3cdb119c8..667837528 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -87,10 +87,10 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
        pColl = sqlGetFuncCollSeq(context);
        assert(mask == -1 || mask == 0);
        iBest = 0;
-       if (sql_value_mp_type(argv[0]) == MP_NIL)
+       if (sql_value_type(argv[0]) == MP_NIL)
                return;
        for (i = 1; i < argc; i++) {
-               if (sql_value_mp_type(argv[i]) == MP_NIL)
+               if (sql_value_type(argv[i]) == MP_NIL)
                        return;
                if ((sqlMemCompare(argv[iBest], argv[i], pColl) ^ mask) >=
                    0) {
@@ -109,7 +109,7 @@ typeofFunc(sql_context * context, int NotUsed, sql_value ** argv)
 {
        const char *z = 0;
        UNUSED_PARAMETER(NotUsed);
-       switch (sql_value_mp_type(argv[0])) {
+       switch (sql_value_type(argv[0])) {
        case MP_INT:
                z = "integer";
                break;
@@ -139,7 +139,7 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
 
        assert(argc == 1);
        UNUSED_PARAMETER(argc);
-       switch (sql_value_mp_type(argv[0])) {
+       switch (sql_value_type(argv[0])) {
        case MP_BIN:
        case MP_INT:
        case MP_DOUBLE:{
@@ -173,7 +173,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 {
        assert(argc == 1);
        UNUSED_PARAMETER(argc);
-       switch (sql_value_mp_type(argv[0])) {
+       switch (sql_value_type(argv[0])) {
        case MP_INT:{
                        i64 iVal = sql_value_int64(argv[0]);
                        if (iVal < 0) {
@@ -229,8 +229,8 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
        UNUSED_PARAMETER(argc);
        struct Mem *needle = argv[0];
        struct Mem *haystack = argv[1];
-       enum mp_type needle_type = sql_value_mp_type(needle);
-       enum mp_type haystack_type = sql_value_mp_type(haystack);
+       enum mp_type needle_type = sql_value_type(needle);
+       enum mp_type haystack_type = sql_value_type(haystack);
 
        if (haystack_type == MP_NIL || needle_type == MP_NIL)
                return;
@@ -398,12 +398,12 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
        int negP2 = 0;
 
        assert(argc == 3 || argc == 2);
-       if (sql_value_mp_type(argv[1]) == MP_NIL
-           || (argc == 3 && sql_value_mp_type(argv[2]) == MP_NIL)
+       if (sql_value_type(argv[1]) == MP_NIL
+           || (argc == 3 && sql_value_type(argv[2]) == MP_NIL)
            ) {
                return;
        }
-       p0type = sql_value_mp_type(argv[0]);
+       p0type = sql_value_type(argv[0]);
        p1 = sql_value_int(argv[1]);
        if (p0type == MP_BIN) {
                len = sql_value_bytes(argv[0]);
@@ -507,13 +507,13 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
        char *zBuf;
        assert(argc == 1 || argc == 2);
        if (argc == 2) {
-               if (MP_NIL == sql_value_mp_type(argv[1]))
+               if (MP_NIL == sql_value_type(argv[1]))
                        return;
                n = sql_value_int(argv[1]);
                if (n < 0)
                        n = 0;
        }
-       if (sql_value_mp_type(argv[0]) == MP_NIL)
+       if (sql_value_type(argv[0]) == MP_NIL)
                return;
        r = sql_value_double(argv[0]);
        /* If Y==0 and X will fit in a 64-bit int,
@@ -900,8 +900,8 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
        int nPat;
        sql *db = sql_context_db_handle(context);
        int is_like_ci = SQL_PTR_TO_INT(sql_user_data(context));
-       int rhs_type = sql_value_mp_type(argv[0]);
-       int lhs_type = sql_value_mp_type(argv[1]);
+       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)
@@ -1018,7 +1018,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 {
        assert(argc == 1);
        UNUSED_PARAMETER(argc);
-       switch (sql_value_mp_type(argv[0])) {
+       switch (sql_value_type(argv[0])) {
        case MP_DOUBLE:{
                        double r1, r2;
                        char zBuf[50];
@@ -1092,7 +1092,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
                        break;
                }
        default:{
-                       assert(sql_value_mp_type(argv[0]) == MP_NIL);
+                       assert(sql_value_type(argv[0]) == MP_NIL);
                        sql_result_text(context, "NULL", 4, SQL_STATIC);
                        break;
                }
@@ -1228,13 +1228,13 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
        assert(zStr == sql_value_text(argv[0]));        /* No encoding change */
        zPattern = sql_value_text(argv[1]);
        if (zPattern == 0) {
-               assert(sql_value_mp_type(argv[1]) == MP_NIL
+               assert(sql_value_type(argv[1]) == MP_NIL
                       || sql_context_db_handle(context)->mallocFailed);
                return;
        }
        nPattern = sql_value_bytes(argv[1]);
        if (nPattern == 0) {
-               assert(sql_value_mp_type(argv[1]) != MP_NIL);
+               assert(sql_value_type(argv[1]) != MP_NIL);
                sql_result_value(context, argv[0]);
                return;
        }
@@ -1302,7 +1302,7 @@ trimFunc(sql_context * context, int argc, sql_value ** argv)
        unsigned char **azChar = 0;     /* Individual characters in zCharSet */
        int nChar;              /* Number of characters in zCharSet */
 
-       if (sql_value_mp_type(argv[0]) == MP_NIL) {
+       if (sql_value_type(argv[0]) == MP_NIL) {
                return;
        }
        zIn = sql_value_text(argv[0]);
@@ -1499,7 +1499,7 @@ sumStep(sql_context * context, int argc, sql_value ** argv)
        UNUSED_PARAMETER(argc);
        p = sql_aggregate_context(context, sizeof(*p));
        assert(p != NULL);
-       enum mp_type type = sql_value_mp_type(argv[0]);
+       enum mp_type type = sql_value_type(argv[0]);
        if (type == MP_NIL)
                return;
        if (type != MP_DOUBLE && type != MP_INT) {
@@ -1510,7 +1510,7 @@ sumStep(sql_context * context, int argc, sql_value ** argv)
                        context->isError = SQL_TARANTOOL_ERROR;
                        return;
                }
-               type = sql_value_mp_type(argv[0]);
+               type = sql_value_type(argv[0]);
        }
        p->cnt++;
        if (type == MP_INT) {
@@ -1578,7 +1578,7 @@ countStep(sql_context * context, int argc, sql_value ** argv)
 {
        CountCtx *p;
        p = sql_aggregate_context(context, sizeof(*p));
-       if ((argc == 0 || MP_NIL != sql_value_mp_type(argv[0])) && p) {
+       if ((argc == 0 || MP_NIL != sql_value_type(argv[0])) && p) {
                p->n++;
        }
 }
@@ -1605,7 +1605,7 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
        if (!pBest)
                return;
 
-       if (sql_value_mp_type(argv[0]) == MP_NIL) {
+       if (sql_value_type(argv[0]) == MP_NIL) {
                if (pBest->flags)
                        sqlSkipAccumulatorLoad(context);
        } else if (pBest->flags) {
@@ -1657,7 +1657,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
        const char *zSep;
        int nVal, nSep;
        assert(argc == 1 || argc == 2);
-       if (sql_value_mp_type(argv[0]) == MP_NIL)
+       if (sql_value_type(argv[0]) == MP_NIL)
                return;
        pAccum =
            (StrAccum *) sql_aggregate_context(context, sizeof(*pAccum));
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 9134f767d..3b15d9775 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -456,7 +456,7 @@ const unsigned char *
 sql_value_text(sql_value *);
 
 enum mp_type
-sql_value_mp_type(sql_value *);
+sql_value_type(sql_value *);
 
 sql *
 sql_context_db_handle(sql_context *);
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 6e867ca84..149f3d047 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -244,7 +244,7 @@ sql_value_text(sql_value * pVal)
  * point number string BLOB NULL
  */
 enum mp_type
-sql_value_mp_type(sql_value *pVal)
+sql_value_type(sql_value *pVal)
 {
        switch (pVal->flags & MEM_PURE_TYPE_MASK) {
        case MEM_Int: return MP_INT;
@@ -1013,7 +1013,7 @@ sql_column_value(sql_stmt * pStmt, int i)
 enum mp_type
 sql_column_type(sql_stmt * pStmt, int i)
 {
-       enum mp_type type = sql_value_mp_type(columnMem(pStmt, i));
+       enum mp_type type = sql_value_type(columnMem(pStmt, i));
        columnMallocFailure(pStmt);
        return type;
 }
@@ -1388,7 +1388,7 @@ int
 sql_bind_value(sql_stmt * pStmt, int i, const sql_value * pValue)
 {
        int rc;
-       switch (sql_value_mp_type((sql_value *) pValue)) {
+       switch (sql_value_type((sql_value *) pValue)) {
        case MP_INT:{
                        rc = sql_bind_int64(pStmt, i, pValue->u.i);
                        break;
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index a80c7a0ab..30017b080 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -290,7 +290,7 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
                pVal =
                    sqlVdbeGetBoundValue(pReprepare, iCol,
                                             FIELD_TYPE_SCALAR);
-               if (pVal && sql_value_mp_type(pVal) == MP_STR) {
+               if (pVal && sql_value_type(pVal) == MP_STR) {
                        z = (char *)sql_value_text(pVal);
                }


>> 	    || eType == SQL_INTEGER) {
>> 		setRawDateNumber(p, sql_value_double(argv[0]));
>> 	} else {
>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index 9adfeec67..3cdb119c8 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -87,10 +87,10 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
>> 	pColl = sqlGetFuncCollSeq(context);
>> 	assert(mask == -1 || mask == 0);
>> 	iBest = 0;
>> -	if (sql_value_type(argv[0]) == SQL_NULL)
>> +	if (sql_value_mp_type(argv[0]) == MP_NIL)
> 
> 3. Please, move that check into a separate static inline
> function in a header file. It appeared to be used often.
> A possible name 'sql_value_is_null’.

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 3cdb119c8..66f601346 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -87,10 +87,10 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
        pColl = sqlGetFuncCollSeq(context);
        assert(mask == -1 || mask == 0);
        iBest = 0;
-       if (sql_value_mp_type(argv[0]) == MP_NIL)
+       if (sql_value_is_null(argv[0]))
                return;
        for (i = 1; i < argc; i++) {
-               if (sql_value_mp_type(argv[i]) == MP_NIL)
+               if (sql_value_is_null(argv[i]))
                        return;
                if ((sqlMemCompare(argv[iBest], argv[i], pColl) ^ mask) >=
                    0) {
@@ -398,12 +398,12 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
        int negP2 = 0;
 
        assert(argc == 3 || argc == 2);
-       if (sql_value_mp_type(argv[1]) == MP_NIL
-           || (argc == 3 && sql_value_mp_type(argv[2]) == MP_NIL)
+       if (sql_value_is_null(argv[1])
+           || (argc == 3 && sql_value_is_null(argv[2]))
            ) {
                return;
        }
@@ -507,13 +507,13 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
        char *zBuf;
        assert(argc == 1 || argc == 2);
        if (argc == 2) {
-               if (MP_NIL == sql_value_mp_type(argv[1]))
+               if (sql_value_is_null(argv[1]))
                        return;
                n = sql_value_int(argv[1]);
                if (n < 0)
                        n = 0;
        }
-       if (sql_value_mp_type(argv[0]) == MP_NIL)
+       if (sql_value_is_null(argv[0]))
                return;
        r = sql_value_double(argv[0]);
        /* If Y==0 and X will fit in a 64-bit int,
@@ -1092,7 +1092,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
                        break;
                }
        default:{
-                       assert(sql_value_mp_type(argv[0]) == MP_NIL);
+                       assert(sql_value_is_null(argv[0]));
                        sql_result_text(context, "NULL", 4, SQL_STATIC);
                        break;
                }
@@ -1228,13 +1228,13 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
        assert(zStr == sql_value_text(argv[0]));        /* No encoding change */
        zPattern = sql_value_text(argv[1]);
        if (zPattern == 0) {
-               assert(sql_value_mp_type(argv[1]) == MP_NIL
+               assert(sql_value_is_null(argv[1])
                       || sql_context_db_handle(context)->mallocFailed);
                return;
        }
        nPattern = sql_value_bytes(argv[1]);
        if (nPattern == 0) {
-               assert(sql_value_mp_type(argv[1]) != MP_NIL);
+               assert(! sql_value_is_null(argv[1]));
                sql_result_value(context, argv[0]);
                return;
        }
@@ -1302,7 +1302,7 @@ trimFunc(sql_context * context, int argc, sql_value ** argv)
        unsigned char **azChar = 0;     /* Individual characters in zCharSet */
        int nChar;              /* Number of characters in zCharSet */
 
-       if (sql_value_mp_type(argv[0]) == MP_NIL) {
+       if (sql_value_is_null(argv[0])) {
                return;
        }
        zIn = sql_value_text(argv[0]);
@@ -1578,7 +1578,7 @@ countStep(sql_context * context, int argc, sql_value ** argv)
 {
        CountCtx *p;
        p = sql_aggregate_context(context, sizeof(*p));
-       if ((argc == 0 || MP_NIL != sql_value_mp_type(argv[0])) && p) {
+       if ((argc == 0 || ! sql_value_is_null(argv[0])) && p) {
                p->n++;
        }
 }
@@ -1605,7 +1605,7 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
        if (!pBest)
                return;
 
-       if (sql_value_mp_type(argv[0]) == MP_NIL) {
+       if (sql_value_is_null(argv[0])) {
                if (pBest->flags)
                        sqlSkipAccumulatorLoad(context);
        } else if (pBest->flags) {
@@ -1657,7 +1657,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
        const char *zSep;
        int nVal, nSep;
        assert(argc == 1 || argc == 2);
-       if (sql_value_mp_type(argv[0]) == MP_NIL)
+       if (sql_value_is_null(argv[0]))
                return;
        pAccum =
            (StrAccum *) sql_aggregate_context(context, sizeof(*pAccum));
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 9134f767d..2189d8c3e 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -456,7 +456,13 @@ const unsigned char *
 sql_value_text(sql_value *);
 
+static inline bool
+sql_value_is_null(sql_value *value)
+{
+       return sql_value_type(value) == MP_NIL;
+}

>> 		return;
>> 	for (i = 1; i < argc; i++) {
>> -		if (sql_value_type(argv[i]) == SQL_NULL)
>> +		if (sql_value_mp_type(argv[i]) == MP_NIL)
>> 			return;
>> 		if ((sqlMemCompare(argv[iBest], argv[i], pColl) ^ mask) >=
>> 		    0) {
>> @@ -139,15 +139,15 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
>> 
>> 	assert(argc == 1);
>> 	UNUSED_PARAMETER(argc);
>> -	switch (sql_value_type(argv[0])) {
>> -	case SQL_BLOB:
>> -	case SQL_INTEGER:
>> -	case SQL_FLOAT:{
>> +	switch (sql_value_mp_type(argv[0])) {
>> +	case MP_BIN:
>> +	case MP_INT:
>> +	case MP_DOUBLE:{
> 
> 4. Probably you could fix part of this:
> https://github.com/tarantool/tarantool/issues/4159 in scope of
> this commit, alongside.

THis patch provides straightforward refactoring, I don’t want
to involve here non-trivial changes.

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

* [tarantool-patches] Re: [PATCH 4/9] sql: introduce type boolean
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-04-18 17:54     ` n.pettik
  2019-04-22 18:02       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 42+ messages in thread
From: n.pettik @ 2019-04-18 17:54 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index 7724e9415..c6fbb1af6 100644
>> --- a/src/box/sql/build.c
>> +++ b/src/box/sql/build.c
>> @@ -958,8 +955,7 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id
>> 	sqlVdbeAddOp2(v, OP_IntCopy, reg_seq_id, first_col + 2);
>> 
>> 	/* 3. True, which is 1 in SQL  */
> 
> 1. I guess, it is not '1' anymore.

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index c6fbb1af6..9f9f6a0da 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -953,10 +953,7 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id
        
        /* 2. Sequence id  */
        sqlVdbeAddOp2(v, OP_IntCopy, reg_seq_id, first_col + 2);
-
-       /* 3. True, which is 1 in SQL  */
        sqlVdbeAddOp2(v, OP_Bool, true, first_col + 3);
-
        sqlVdbeAddOp3(v, OP_MakeRecord, first_col + 1, 3, first_col);
 
        return first_col;

>> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
>> index 4b98bd175..6b38e8e66 100644
>> --- a/src/box/sql/expr.c
>> +++ b/src/box/sql/expr.c
>> @@ -3360,6 +3360,15 @@ expr_code_int(struct Parse *parse, struct Expr *expr, bool is_neg,
>> 	}
>> }
>> 
> 
> 2. It is worth adding a comment. At a first glance I did not
> understand what is 'mem', and what 'expr' is expected and allowed
> to store - str, int, float?
> 
>> +static void
>> +vdbe_emit_bool(struct Vdbe *v, const struct Expr *expr, int mem)
>> +{
>> +	const char *z = expr->u.zToken;
> 
> 3. It would be safer to add an assertion here that expr actually
> stores a string only.
> 
> 4. The function is quite small, and is called in one place
> only. Mark it 'inline’.

I’ve removed this function at all, see below.

>> @@ -3764,6 +3773,11 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
>> 			expr_code_int(pParse, pExpr, false, target);
>> 			return target;
>> 		}
>> +	case TK_TRUE:
>> +	case TK_FALSE: {
>> +			vdbe_emit_bool(pParse->pVdbe, pExpr, target);
> 
> 5. Correct me, if I am wrong but there is how I understand
> boolean value emission.
> 
> - Parser scans a literal. It marks 'true' with TK_TRUE, 'false'
>   with TK_FALSE (taking into account case-insensitivity);
> 
> - Here you merge TK_FALSE and TK_TRUE into a single 'case'
>   statement;
> 
> - Inside that statement you call vdbe_emit_bool, which again
>   parses the string for being equal 'true' of 'false'.
> 
> So why? Since the first step you already have the boolean value.
> The value is '== TK_TRUE'. You do not need to use strcasecmp here.
> Just split these 'case' statements, and make vdbe_emit_bool()
> taking a boolean value instead of expr.

You are absolutely right. Refactored this simple way:

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 6b38e8e66..d70b64c45 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -3360,15 +3360,6 @@ expr_code_int(struct Parse *parse, struct Expr *expr, bool is_neg,
        }
 }
 
-static void
-vdbe_emit_bool(struct Vdbe *v, const struct Expr *expr, int mem)
-{
-       const char *z = expr->u.zToken;
-       assert(z != NULL);
-       bool val = strncasecmp(z, "TRUE", 4) == 0;
-       sqlVdbeAddOp2(v, OP_Bool, val, mem);
-}
-
 /*
  * Erase column-cache entry number i
  */
@@ -3775,7 +3766,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
                }
        case TK_TRUE:
        case TK_FALSE: {
-                       vdbe_emit_bool(pParse->pVdbe, pExpr, target);
+                       sqlVdbeAddOp2(v, OP_Bool, op == TK_TRUE, target);
                        return target;
                }

> 6. I see, that all other 'case's set pExpr->type. Why don't you
> do it here?

It is required only for non-constant values. For literals it is assigned
in parse.y spanExpr().

> 
>> +			return target;
>> +		}
>> #ifndef SQL_OMIT_FLOATING_POINT
>> 	case TK_FLOAT:{
>> 			pExpr->type = FIELD_TYPE_INTEGER;
>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index 3cdb119c8..860cd8920 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -1091,6 +1101,11 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
>> 			}
>> 			break;
>> 		}
>> +	case MP_BOOL: {
>> +		sql_result_text(context, argv[0]->u.b ? "true" : "false", -1,
>> +				SQL_TRANSIENT);
>> +		break;
>> +	}
>> 	default:{
>> 			assert(sql_value_mp_type(argv[0]) == MP_NIL);
>> 			sql_result_text(context, "NULL", 4, SQL_STATIC);
> 
> 7. In the same file lengthFunc() now accepts boolean value. But obviously
> it should not, bool is not string. It exacerbates 4159 issue.

There are many other functions accepting wrong argument types.
Now length() accepts integers and floats, so lets fix this behaviour
later in scope of #4159.

>> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
>> index 9134f767d..4f62c2782 100644
>> --- a/src/box/sql/sqlInt.h
>> +++ b/src/box/sql/sqlInt.h
>> @@ -2161,6 +2167,24 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
>> 			}
>> 			break;
>> 		}
>> +	} else if ((flags1 | flags3) & MEM_Bool) {
>> +		/*
>> +		 * If one of values is of type BOOLEAN, then the
>> +		 * second one must be BOOLEAN as well. Otherwise
>> +		 * an error is raised.
>> +		 */
>> +		bool is_bool_type_arg1 = flags1 & MEM_Bool;
>> +		bool is_bool_type_arg3 = flags3 & MEM_Bool;
>> +		if (! is_bool_type_arg1 || ! is_bool_type_arg3) {
>> +			char *inconsistent_type = ! is_bool_type_arg1 ?
>> +						  mem_type_to_str(pIn1) :
>> +						  mem_type_to_str(pIn3);
>> +			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
>> +				 inconsistent_type, "boolean");
>> +			rc = SQL_TARANTOOL_ERROR;
>> +			goto abort_due_to_error;
>> +		}
>> +		res = sqlMemCompare(pIn3, pIn1, NULL);
>> 	} else {
> 
> 8. Looking at all these type comparison prohibitions I am getting
> afraid of how will we select from SCALAR columns?
> 
> A schema:
> 
>    box.execute('CREATE TABLE t (id INTEGER PRIMARY KEY, a SCALAR UNIQUE);')
>    box.execute("INSERT INTO t VALUES (1, 1), (2, true), (3, false), (4, 'str')")
> 
> SQL select:
> 
>> box.execute('SELECT * FROM t WHERE a > 1')
>    ---
>    - error: 'Type mismatch: can not convert INTEGER to boolean'
>    ...
> 
> The same Lua select:
> 
>> box.space.T.index[1]:select({1}, {iterator = 'GT'})
>    ---
>    - - [4, 'str']
>    ...
> 
> In fact, now we can not use SCALAR in SQL for any comparisons
> because it will raise type mismatch on literally everything.
> 
> What are we going to do with it? IMO, we could add a flag like
> 'is_scalar' to struct Mem in its flags section, which would allow
> to compare this value with anything.

Konstantin suggested to extend struct Mem with field_type,
which is going to substitute this flag.

> Looks crutchy though. And is
> not a part of this issue of course, but should be filed into the
> issue list.
> 
> I see a similar issue https://github.com/tarantool/tarantool/issues/4124.
> Probably, it is worth extending it instead of opening new.

I consider this as a trade-off of using scalar: users shouldn’t use this
type until they are really have to. Nevertheless, they still can use CAST
operator and switch case, or write their own lua-function converting values
to required type.

>> 		enum field_type type = pOp->p5 & FIELD_TYPE_MASK;
>> 		if (sql_type_is_numeric(type)) {
>> +bool
>> +sql_column_boolean(sql_stmt *stmt, int i)
>> +{
>> +	bool val = sql_value_boolean(columnMem(stmt, i));
>> +	columnMallocFailure(stmt);
>> +	return val;
>> +}
>> +
>> sql_int64
>> sql_column_int64(sql_stmt * pStmt, int i)
>> {
> 
> 9. You did not update sql_bind_value(), now it binds NULL
> on any boolean value. On the other hand it is not used at
> all and probably could be removed.

I will remove it in a separate commit.

>> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
>> index 0cc3c1487..0f56028e5 100644
>> --- a/src/box/sql/vdbeaux.c
>> +++ b/src/box/sql/vdbeaux.c
>> @@ -3383,6 +3383,17 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
>> 		return (f2 & MEM_Null) - (f1 & MEM_Null);
>> 	}
>> 
>> +	if (combined_flags & MEM_Bool) {
> 
> 10. Sorry for nit, but we use explicit != 0 in conditions.

diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 0f56028e5..861d03344 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -3383,7 +3383,7 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
                return (f2 & MEM_Null) - (f1 & MEM_Null);
        }
 
-       if (combined_flags & MEM_Bool) {
+       if ((combined_flags & MEM_Bool) != 0) {
                if ((f1 & f2 & MEM_Bool) != 0) {
                        if (pMem1->u.b == pMem2->u.b)
                                return 0;

>> +		if ((f1 & f2 & MEM_Bool) != 0) {
>> +			if (pMem1->u.b == pMem2->u.b)
>> +				return 0;
>> +			if (pMem1->u.b)
>> +				return 1;
>> +			return -1;
>> +		}
>> +		return -1;
>> +	}
>> +
>> 	/* At least one of the two values is a number
>> 	 */
>> 	if (combined_flags & (MEM_Int | MEM_Real)) {
>> @@ -3561,10 +3572,16 @@ sqlVdbeCompareMsgpack(const char **key1,
>> 			break;
>> 		}
>> 	case MP_BOOL:{
>> -			assert((unsigned char)(*aKey1) == 0xc2
>> -			       || (unsigned char)*aKey1 == 0xc3);
>> -			mem1.u.i = (unsigned)(size_t) aKey1++ - 0xc2;
>> -			goto do_int;
>> +
> 
> 11. Stray empty line.

@@ -3572,7 +3572,6 @@ sqlVdbeCompareMsgpack(const char **key1,
                        break;
                }
        case MP_BOOL:{
-
                        mem1.u.b = mp_decode_bool(&aKey1);
                        if ((pKey2->flags & MEM_Bool) != 0) {
                                if (mem1.u.b != pKey2->u.b) {

>> +			mem1.u.b = mp_decode_bool(&aKey1);
>> +			if ((pKey2->flags & MEM_Bool) != 0) {
>> +				if (mem1.u.b != pKey2->u.b) {
>> +					rc = mem1.u.b ? 1 : -1;
>> +				}
> 
> 12. Usually we omit curly braces when body of a cycle or a condition
> is one line.

@@ -3572,14 +3572,12 @@ sqlVdbeCompareMsgpack(const char **key1,
                        break;
                }
        case MP_BOOL:{
-
                        mem1.u.b = mp_decode_bool(&aKey1);
                        if ((pKey2->flags & MEM_Bool) != 0) {
-                               if (mem1.u.b != pKey2->u.b) {
+                               if (mem1.u.b != pKey2->u.b)
                                        rc = mem1.u.b ? 1 : -1;
-                               }
                        } else {

>> +			} else {
>> +				rc = (pKey2->flags & MEM_Null) != 0 ? +1 : -1;
> 
> 13. '+1' ? Sorry, we do not use prefix '+’.

                        } else {
-                               rc = (pKey2->flags & MEM_Null) != 0 ? +1 : -1;
+                               rc = (pKey2->flags & MEM_Null) != 0 ? 1 : -1;
                        }

>> diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
>> index 15a2f55cb..4fed0eefe 100644
>> --- a/src/box/sql/vdbemem.c
>> +++ b/src/box/sql/vdbemem.c
>> @@ -499,6 +501,16 @@ sqlVdbeRealValue(Mem * pMem, double *v)
>> 	return -1;
>> }
>> 
>> +int
>> +vdbe_value_boolean(Mem *mem, bool *b)
>> +{
>> +	if (mem->flags  & MEM_Bool) {
>> +		*b = mem->u.b;
>> +		return 0;
>> +	}
>> +	return -1;
>> +}
> 
> 14. Probably it is better to name it by Mem, not by
> Vdbe, because it does not take a Vdbe pointer. And
> similar functions contract type names: 'boolean' -> 'bool',
> 'integer' -> 'int', etc. In summary, 'mem_value_bool'
> for example.

diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 5251e76ab..73c77efed 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -510,7 +510,7 @@ int sqlVdbeMemIntegerify(Mem *, bool is_forced);
 int sqlVdbeRealValue(Mem *, double *);
 
 int
-vdbe_value_boolean(Mem *mem, bool *b);
+mem_value_bool(Mem *mem, bool *b);
 
 int mem_apply_integer_type(Mem *);
 int sqlVdbeMemRealify(Mem *);
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index f305974bc..4bdbcafd4 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -215,7 +215,7 @@ bool
 sql_value_boolean(sql_value *val)
 {
        bool b;
-       vdbe_value_boolean((struct Mem *) val, &b);
+       mem_value_bool((struct Mem *) val, &b);
        return b;
 }
 
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 4fed0eefe..92406f938 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -502,7 +502,7 @@ sqlVdbeRealValue(Mem * pMem, double *v)
 }
 
 int
-vdbe_value_boolean(Mem *mem, bool *b)
+mem_value_bool(Mem *mem, bool *b)
 {
        if (mem->flags  & MEM_Bool) {
                *b = mem->u.b;

>> @@ -594,6 +606,37 @@ sqlVdbeMemNumerify(Mem * pMem)
>> 	return SQL_OK;
>> }
>> 
>> +/**
>> + * According to ANSI SQL string value can be converted to boolean
>> + * type if string consists of literal "true" or "false" and
>> + * number of leading spaces.
>> + *
>> + * For instance, "   tRuE" can be successfully converted to
>> + * boolean value true.
> 15. So '   true' is valid, but 'true    ' is not? As I see the
> standard, it is not so. Cite:
> 
> """
>    If TD is boolean, then
> 
>    Case:
> 
>        a) If SD is character string, then SV is replaced by
> 
>            TRIM ( BOTH ' ' FROM VE )
> 
>           Case:
> 
>            i)  If the rules for <literal> in Subclause 5.3, “<literal>”,
>                can be applied to SV to determine a valid value of the
>                data type TD, then let TV be that value.
> 
>            ii) Otherwise, an exception condition is raised: data
>                exception — invalid character value for cast.
> 
>        b) If SD is boolean, then TV is SV.
> """
> 
> TD - cast type,
> SD - type of cast target,
> VE,SV - cast target,
> TV - cast result value.
> 
> The key line: "TRIM ( BOTH ' ' FROM VE )" -> 'BOTH'.
> Spaces are trimmed off from both sides.

Yep, you are right. For some reason I’ve missed the fact
that spaces must be removed from both sides.

>> + *
>> + * @param str String to be converted to boolean.
>> + *            Assumed to be null terminated.
>> + * @param result Resulting value of cast.
>> + * @retval 0 If string satisfies conditions above.
>> + * @retval -1 Otherwise.
>> + */
>> +static int
>> +str_cast_to_boolean(const char *str, bool *result)
>> +{
>> +	assert(str != NULL);
>> +	for (; *str == ' '; str++);
>> +	size_t rest_str_len = strlen(str);
>> +	if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
>> +		*result = true;
>> +		return 0;
>> +	}
>> +	if (rest_str_len == 5 && strncasecmp(str, "false", 5) == 0) {
>> +		*result = false;
>> +		return 0;
>> +	}
> 
> 16. I guess it is far from the most efficient implementation. You
> calculate length of the whole string before comparison, scanning
> its twice in the worst and most common case. Please, consider my
> fixes below. I did not test them though, so probably there are some
> typos.
> Note, that in my fixes I've already accounted my comment about
> trimming trailing whitespaces.
> 
> ============================================================================
> @@ -625,16 +625,20 @@ str_cast_to_boolean(const char *str, bool *result)
> {
> 	assert(str != NULL);
> 	for (; *str == ' '; str++);
> -	size_t rest_str_len = strlen(str);
> -	if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
> +	if (strncasecmp(str, "true", 4) == 0) {

We can’t use strncasecmp() without verification the fact that
rest_str_len (i.e. string length after skipping leading spaces) is >= 4.

> +		str += 4;
> 		*result = true;
> -		return 0;
> -	}
> -	if (rest_str_len == 5 && strncasecmp(str, "false", 5) == 0) {
> +	} else if (strncasecmp(str, "false", 5) == 0) {
> +		str += 5;
> 		*result = false;
> -		return 0;
> +	} else {
> +		return -1;
> 	}
> -	return -1;
> +	for (; *str != 0; ++str) {
> +		if (*str != ' ')
> +			return -1;
> +	}
> +	return 0;
> }
> ============================================================================

Taking into account my comment diff is next:

diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 92406f938..19b42b992 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -609,9 +609,9 @@ sqlVdbeMemNumerify(Mem * pMem)
 /**
  * According to ANSI SQL string value can be converted to boolean
  * type if string consists of literal "true" or "false" and
- * number of leading spaces.
+ * number of leading and trailing spaces.
  *
- * For instance, "   tRuE" can be successfully converted to
+ * For instance, "   tRuE  " can be successfully converted to
  * boolean value true.
  *
  * @param str String to be converted to boolean.
@@ -626,15 +626,20 @@ str_cast_to_boolean(const char *str, bool *result)
        assert(str != NULL);
        for (; *str == ' '; str++);
        size_t rest_str_len = strlen(str);
-       if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
+       if (rest_str_len >= 4 && strncasecmp(str, "true", 4) == 0) {
                *result = true;
-               return 0;
-       }
-       if (rest_str_len == 5 && strncasecmp(str, "false", 5) == 0) {
+               str += 4;
+       } else if (rest_str_len >= 5 && strncasecmp(str, "false", 5) == 0) {
                *result = false;
-               return 0;
+               str += 5;
+       } else {
+               return -1;
        }
-       return -1;
+       for (; *str != '\0'; ++str) {
+               if (*str != ' ')
+                       return -1;
+       }
+       return 0;
 }
 
And a couple of tests:

diff --git a/test/sql/types.result b/test/sql/types.result
index 3aa0169e2..673a61bb4 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -796,6 +796,18 @@ box.execute("SELECT CAST('  TrUe' AS BOOLEAN);")
   rows:
   - [true]
 ...
+box.execute("SELECT CAST('  falsE    ' AS BOOLEAN);")
+---
+- metadata:
+  - name: CAST('  falsE    ' AS BOOLEAN)
+    type: boolean
+  rows:
+  - [false]
+...
+box.execute("SELECT CAST('  fals' AS BOOLEAN);")
+---
+- error: 'Type mismatch: can not convert   fals to boolean'
+...
 box.execute("SELECT CAST(X'4D6564766564' AS BOOLEAN);")
 ---
 - error: 'Type mismatch: can not convert Medved to boolean'
diff --git a/test/sql/types.test.lua b/test/sql/types.test.lua
index 2aed0fe94..461635978 100644
--- a/test/sql/types.test.lua
+++ b/test/sql/types.test.lua
@@ -188,6 +188,9 @@ box.execute("SELECT CAST(1 AS BOOLEAN);")
 box.execute("SELECT CAST(1.123 AS BOOLEAN);")
 box.execute("SELECT CAST('abc' AS BOOLEAN);")
 box.execute("SELECT CAST('  TrUe' AS BOOLEAN);")
+box.execute("SELECT CAST('  falsE    ' AS BOOLEAN);")
+box.execute("SELECT CAST('  fals' AS BOOLEAN);")
+
 box.execute("SELECT CAST(X'4D6564766564' AS BOOLEAN);")

>>  * Cast the datatype of the value in pMem according to the type
>>  * @type.  Casting is different from applying type in that a cast
>> diff --git a/test/sql/types.result b/test/sql/types.result
>> index 0a5085658..3aa0169e2 100644
>> --- a/test/sql/types.result
>> +++ b/test/sql/types.result
>> +box.execute("SELECT unknown;")
>> +---
>> +- metadata:
>> +  - name: unknown
>> +    type: scalar
> 
> 17. Should not it be boolean?

SELECT NULL; also gives as type SCALAR.
UNKNOWN is an alias to NULL.

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

* [tarantool-patches] Re: [PATCH 5/9] sql: improve type determination for column meta
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-04-18 17:54     ` n.pettik
  2019-04-22 18:02       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 42+ messages in thread
From: n.pettik @ 2019-04-18 17:54 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

[-- Attachment #1: Type: text/plain, Size: 2526 bytes --]



> On 16 Apr 2019, at 17:12, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> On 14/04/2019 18:04, Nikita Pettik wrote:
>> In most cases we don't assign and store type of node of expression AST
>> (except for constant literals).  To determine type of node  we use
>> sql_expr_type() function, which implements logic of quite simple
>> recursive tree traversal. Before this patch we set type of node after
>> code generation in sqlExprCodeTarget() without any traversal. This
>> approach is way worse even then sql_expr_type().
> 
> Why? How is recursive scan of the tree better than simple access to
> a struct's attribute? I see, that your patch improves type determination
> precision, but I do not understand how, and what a perf price for that?

Firstly, it seems that sqlExprCodeTarget() can be called on copy of
original expr (when query contains order/group by clauses): I see
that types assigned in that function are different from types in expr
passed to generateColumnNames().

Secondly, suggested approach simply makes code cleaner and easier
to maintain: we shouldn’t care about adding on each branch of switch
appropriate type (since it is already done in sql_expr_type()).

Eventually, sqlExprCodeTarget() doesn’t take into consideration
types of child nodes. This drawback will appear when we extend
integer type with small/big ints:

Column a is of type smallint.

SELECT 1 + a;

Should result in ‘smallint' type, not common ‘number’.
Without tree traversal we can’t do that.

Price is a primitive traversal through the expr tree. This traversal (but in more
sophisticated implementation and being called only once saving in each node
its type) is anyway required to implement this task:
https://github.com/tarantool/tarantool/issues/3103 <https://github.com/tarantool/tarantool/issues/3103>

In fact, we should once visit each node (probably after names resolution)
and save its type depending on child’s types.

> If you are not sure about the price, we could ask Alexander to run
> benches on your branch before pushing into the master.

This patch is auxiliary to main-patchset. I’ve added it to avoid
abusing changing result files like sql/types.result etc. Can drop it tho.

>> So, to improve accuracy
>> of type determination, let's always call that method and remove type
>> assignment in sqlExprCodeTarget().
> 
> This patch fixes the issue
> https://github.com/tarantool/tarantool/issues/4126.

Ok, added label.


[-- Attachment #2: Type: text/html, Size: 3904 bytes --]

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

* [tarantool-patches] Re: [PATCH 6/9] sql: make comparison predicate return boolean
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-04-18 17:54     ` n.pettik
  0 siblings, 0 replies; 42+ messages in thread
From: n.pettik @ 2019-04-18 17:54 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy



> On 16 Apr 2019, at 17:12, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Thanks for the patch! See 3 comments below.
> 
> On 14/04/2019 18:04, Nikita Pettik wrote:
>> 
>> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
>> index 10794b18e..43d4262e5 100644
>> --- a/src/box/sql/vdbe.c
>> +++ b/src/box/sql/vdbe.c
>> @@ -2273,8 +2273,8 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
>> 			if ((pOp->opcode==OP_Eq)==res2) break;
>> 		}
>> 		memAboutToChange(p, pOut);
>> -		MemSetTypeFlag(pOut, MEM_Int);
>> -		pOut->u.i = res2;
>> +		MemSetTypeFlag(pOut, MEM_Bool);
>> +		pOut->u.b = res2;
> 
> 1. Please, update the comments about SQL_STOREP2 and SQL_KEEPNULL.
> They still say the result is stored as int 1/0.

Do you mean these comments (?) :

diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 43d4262e5..b28e9b4ea 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2061,8 +2061,8 @@ case OP_Cast: {                  /* in1 */
  * registers P1 and P3.
  *
  * If both SQL_STOREP2 and SQL_KEEPNULL flags are set then the
- * content of r[P2] is only changed if the new value is NULL or 0 (false).
- * In other words, a prior r[P2] value will not be overwritten by 1 (true).
+ * content of r[P2] is only changed if the new value is NULL or false.
+ * In other words, a prior r[P2] value will not be overwritten by true.
  */
 /* Opcode: Ne P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]!=r[P1]
@@ -2072,15 +2072,15 @@ case OP_Cast: {                  /* in1 */
  * additional information.
  *
  * If both SQL_STOREP2 and SQL_KEEPNULL flags are set then the
- * content of r[P2] is only changed if the new value is NULL or 1 (true).
- * In other words, a prior r[P2] value will not be overwritten by 0 (false).
+ * content of r[P2] is only changed if the new value is NULL or true.
+ * In other words, a prior r[P2] value will not be overwritten by false.
  */
 /* Opcode: Lt P1 P2 P3 P4 P5
  * Synopsis: IF r[P3]<r[P1]
  *
  * Compare the values in register P1 and P3.  If reg(P3)<reg(P1) then
  * jump to address P2.  Or if the SQL_STOREP2 flag is set in P5 store
- * the result of comparison (0 or 1 or NULL) into register P2.
+ * the result of comparison (false or true or NULL) into register P2.
  *
  * If the SQL_JUMPIFNULL bit of P5 is set and either reg(P1) or
  * reg(P3) is NULL then the take the jump.  If the SQL_JUMPIFNULL

> 
>> 		REGISTER_TRACE(pOp->p2, pOut);
>> 	} else {
>> 		VdbeBranchTaken(res!=0, (pOp->p5 & SQL_NULLEQ)?2:3);
>> diff --git a/test/sql-tap/aggnested.test.lua b/test/sql-tap/aggnested.test.lua
>> index 656576b70..67a9ba891 100755
>> --- a/test/sql-tap/aggnested.test.lua
>> +++ b/test/sql-tap/aggnested.test.lua
>> @@ -215,7 +215,7 @@ test:do_execsql_test("aggnested-3.2-2",
>>     ]],
>>     {
>>         -- <aggnested-3.2>
>> -        0
>> +        ""
> 
> 2. Why? There 'sum' was selected. It can not return text.

I guess because nulls are converted this way in execsql function.
SUM() now is not called at all (in previous test version it was called
with 0 as arg), so query returns NULL.

>> @@ -227,13 +227,13 @@ test:do_execsql_test("aggnested-3.3",
>>         INSERT INTO t1 VALUES(4469,2),(4469,1);
>>         CREATE TABLE t2 (value2 INT PRIMARY KEY);
>>         INSERT INTO t2 VALUES(1);
>> -        SELECT (SELECT sum(value2=value1) FROM t2), max(value1)
>> +        SELECT (SELECT sum(value1) FROM t2 where value1=value2), max(value1)
>>           FROM t1
>>          GROUP BY id1;
>>     ]],
>>     {
>>         -- <aggnested-3.3>
>> -        0, 2
>> +        "", 2
> 
> 3. The same.
> 
>>         -- </aggnested-3.3>
>>     })
>> 

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

* [tarantool-patches] Re: [PATCH 7/9] sql: make predicates accept and return boolean
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-04-18 17:55     ` n.pettik
  0 siblings, 0 replies; 42+ messages in thread
From: n.pettik @ 2019-04-18 17:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
>> index e4de472a8..c5ec55de2 100644
>> --- a/src/box/sql/expr.c
>> +++ b/src/box/sql/expr.c
>> @@ -1134,7 +1130,10 @@ sql_and_expr_new(struct sql *db, struct Expr *left_expr,
>> 	} else if (exprAlwaysFalse(left_expr) || exprAlwaysFalse(right_expr)) {
>> 		sql_expr_delete(db, left_expr, false);
>> 		sql_expr_delete(db, right_expr, false);
>> -		return sql_expr_new(db, TK_INTEGER, &sqlIntTokens[0]);
>> +		struct Expr *f = sql_expr_new(db, TK_FALSE,
>> +					      &sql_boolean_tokens[0]);
>> +		f->type = FIELD_TYPE_BOOLEAN;
> 
> 1. No check for f == NULL.

Fixed:

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 27ab8f5d7..354f02948 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -1130,9 +1130,9 @@ sql_and_expr_new(struct sql *db, struct Expr *left_expr,
        } else if (exprAlwaysFalse(left_expr) || exprAlwaysFalse(right_expr)) {
                sql_expr_delete(db, left_expr, false);
                sql_expr_delete(db, right_expr, false);
-               struct Expr *f = sql_expr_new(db, TK_FALSE,
-                                             &sql_boolean_tokens[0]);
-               f->type = FIELD_TYPE_BOOLEAN;
+               struct Expr *f = sql_expr_new_anon(db, TK_FALSE);
+               if (f != NULL)
+                       f->type = FIELD_TYPE_BOOLEAN;
                return f;
        } else {
                struct Expr *new_expr = sql_expr_new_anon(db, TK_AND);


> 2. Why do you need f->type at all, if anyway you use sql_expr_type()
> to get the type? Why isn't this field dropped still?

It is leaf (terminal node of tree), so it should be assigned with type.

>> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
>> index f64a84948..b520b23d5 100644
>> --- a/src/box/sql/parse.y
>> +++ b/src/box/sql/parse.y
>> @@ -1237,11 +1237,13 @@ expr(A) ::= expr(A) in_op(N) LP exprlist(Y) RP(E). [IN] {
>>     ** regardless of the value of expr1.
>>     */
>>     sql_expr_delete(pParse->db, A.pExpr, false);
>> -    A.pExpr = sql_expr_new_dequoted(pParse->db, TK_INTEGER, &sqlIntTokens[N]);
>> +    int tk = N == 0 ? TK_FALSE : TK_TRUE;
>> +    A.pExpr = sql_expr_new_dequoted(pParse->db, tk, &sql_boolean_tokens[N]);
> 
> 3. Why do you use 'dequoted'? Do you store not normalized names in
> sql_boolean_tokens? If you do, then why not to store already
> normalized names and use sql_expr_new() here so as to avoid ICU calls?
> 
> A normalized name is always uppercased, but sql_boolean_tokens stores
> lowercase strings. I tried to do this:
> 
>    -    A.pExpr = sql_expr_new_dequoted(pParse->db, tk, &sql_boolean_tokens[N]);
>    +    A.pExpr = sql_expr_new(pParse->db, tk, &sql_boolean_tokens[N]);
> 
> And the tests passed. Why?

> Another question - why boolean constant needs zToken at all? As I understand,
> the only reason is that sqlExprCodeTarget parses zToken again even having ready
> at hand TK_FALSE and TK_TRUE values. If I am right, you can drop boolean_tokens
> array once you've fixed my comment 5 from the main patch (4/9).

Yep, you are absolutely right: now we don’t care about zToken
and can operate only on token type. I’ve removed sql_boolean_tokens.

> The same question could be asked for integer constants stored in struct Expr
> and having zToken. The only reason for that is the delayed parsing of the tokens,
> as I understand. First they all are stored in Expr.zToken, and then parsed in
> CodeTarget. But it would not be necessary if we parsed them right after reading and
> storing actual value into Expr.u.iValue. In such a case zToken would be used for
> identifiers and strings only, not for constants which I think appear quite often.
> The reasoning behind my proposal is that parsing of an integer is likely to be
> faster itself than calling ICU normalization functions.
> 
> The same can be said about float/double values, but I do not see such a type in
> Expr.u.

I guess this may turn out to be fair. I’ve opened issue:

https://github.com/tarantool/tarantool/issues/4172

> Of course, all these integer/float-related thoughts are not about this patchset.
> Probably, it is worth opening a separate issue.
> 
>>     if (A.pExpr == NULL) {
>>       pParse->is_aborted = true;
>>       return;
>>     }
>> +    A.pExpr->type = FIELD_TYPE_BOOLEAN;
>>   }else if( Y->nExpr==1 ){
>>     /* Expressions of the form:
>>     **
>> diff --git a/test/sql-tap/cse.test.lua b/test/sql-tap/cse.test.lua
>> index 4b25f936d..39c1cc4ca 100755
>> --- a/test/sql-tap/cse.test.lua
>> +++ b/test/sql-tap/cse.test.lua
>> @@ -132,7 +132,7 @@ test:do_execsql_test(
>> test:do_execsql_test(
>>     "cse-1.7",
>>     [[
>> -        SELECT a, -a, ~a, NOT a, NOT NOT a, a-a, a+a, a*a, a/a, a FROM t1
>> +        SELECT a, -a, ~a, NOT CAST(a AS BOOLEAN), NOT NOT CAST(a AS BOOLEAN), a-a, a+a, a*a, a/a, a FROM t1
>>     ]], {
>>         -- <cse-1.7>
>>         1, -1, -2, 0, 1, 0, 2, 1, 1, 1, 2, -2, -3, 0, 1, 0, 4, 4, 1, 2
> 
> 4. Above the fourth selected column is 'NOT CAST(a AS BOOLEAN)'. It is either true or
> false, but in the result set it is 0. Why? First I thought, that probably 0 == false
> in Lua, but it is not as a test showed:

I guess because execsql implicitly converts true -> 1, false -> 0.
This is quite convenient way since we don’t have to fix test results.
I can open QA issue to avoid this conversion and make tests clearer.

> tarantool> 0 == false
> ---
> - false
> ...
> 
> tarantool> 1 == true
> ---
> - false
> ...
> 
>> diff --git a/test/sql-tap/tkt3541.test.lua b/test/sql-tap/tkt3541.test.lua
>> index 08a9be557..05247728d 100755
>> --- a/test/sql-tap/tkt3541.test.lua
>> +++ b/test/sql-tap/tkt3541.test.lua
>> @@ -39,7 +39,7 @@ test:do_test(
>>     "tkt3541-1.2",
>>     function()
>>         return test:execsql [[
>> -            SELECT CASE NOT max(x) WHEN min(x) THEN 1 ELSE max(x) END FROM t1;
>> +            SELECT CASE max(x) = 0 WHEN min(x) > 0 THEN 1 ELSE max(x) END FROM t1;
> 
> 5. If negative numbers are not treated as FALSE, then I
> guess you meant 'min(x) <> 0’.

diff --git a/test/sql-tap/tkt3541.test.lua b/test/sql-tap/tkt3541.test.lua
index 05247728d..0d76e7744 100755
--- a/test/sql-tap/tkt3541.test.lua
+++ b/test/sql-tap/tkt3541.test.lua
@@ -39,7 +39,7 @@ test:do_test(
     "tkt3541-1.2",
     function()
         return test:execsql [[
-            SELECT CASE max(x) = 0 WHEN min(x) > 0 THEN 1 ELSE max(x) END FROM t1;
+            SELECT CASE max(x) = 0 WHEN min(x) <> 0 THEN 1 ELSE max(x) END FROM t1;
         ]]
     end, {


> My total diff which I am not sure why does not break tests:

Partially applied (also see diff and explanation above):

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index b520b23d5..a94414ede 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1238,7 +1238,7 @@ expr(A) ::= expr(A) in_op(N) LP exprlist(Y) RP(E). [IN] {
     */
     sql_expr_delete(pParse->db, A.pExpr, false);
     int tk = N == 0 ? TK_FALSE : TK_TRUE;
-    A.pExpr = sql_expr_new_dequoted(pParse->db, tk, &sql_boolean_tokens[N]);
+    A.pExpr = sql_expr_new_anon(pParse->db, tk);
     if (A.pExpr == NULL) {
       pParse->is_aborted = true;
       return;


> ==============================================================================
> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index c5ec55de2..42b60babd 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -1130,10 +1130,7 @@ sql_and_expr_new(struct sql *db, struct Expr *left_expr,
> 	} else if (exprAlwaysFalse(left_expr) || exprAlwaysFalse(right_expr)) {
> 		sql_expr_delete(db, left_expr, false);
> 		sql_expr_delete(db, right_expr, false);
> -		struct Expr *f = sql_expr_new(db, TK_FALSE,
> -					      &sql_boolean_tokens[0]);
> -		f->type = FIELD_TYPE_BOOLEAN;
> -		return f;
> +		return sql_expr_new_anon(db, TK_FALSE);
> 	} else {
> 		struct Expr *new_expr = sql_expr_new_anon(db, TK_AND);
> 		sqlExprAttachSubtrees(db, new_expr, left_expr, right_expr);
> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
> index b520b23d5..f75999c35 100644
> --- a/src/box/sql/parse.y
> +++ b/src/box/sql/parse.y
> @@ -1238,12 +1238,11 @@ expr(A) ::= expr(A) in_op(N) LP exprlist(Y) RP(E). [IN] {
>     */
>     sql_expr_delete(pParse->db, A.pExpr, false);
>     int tk = N == 0 ? TK_FALSE : TK_TRUE;
> -    A.pExpr = sql_expr_new_dequoted(pParse->db, tk, &sql_boolean_tokens[N]);
> +    A.pExpr = sql_expr_new(pParse->db, tk, &sql_boolean_tokens[N]);
>     if (A.pExpr == NULL) {
>       pParse->is_aborted = true;
>       return;
>     }
> -    A.pExpr->type = FIELD_TYPE_BOOLEAN;
>   }else if( Y->nExpr==1 ){
>     /* Expressions of the form:
>     **
> 
> ==============================================================================

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

* [tarantool-patches] Re: [PATCH 8/9] sql: make LIKE predicate return boolean result
  2019-04-16 14:12   ` [tarantool-patches] Re: [PATCH 8/9] sql: make LIKE predicate return boolean result Vladislav Shpilevoy
@ 2019-04-18 17:55     ` n.pettik
  2019-04-22 18:02       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 42+ messages in thread
From: n.pettik @ 2019-04-18 17:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index 860cd8920..baa739ba4 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -974,7 +974,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
>> 		sql_result_error(context, err_msg, -1);
>> 		return;
>> 	}
>> -	sql_result_int(context, res == MATCH);
>> +	sql_result_boolean(context, res == MATCH);
> 
> Probably we should either contract all the names,
> or do not contract any. Here the name was contracted: 'int',
> not 'integer'. Then it should be 'bool', not 'boolean'.

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 268a4768a..238bd8e03 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -974,7 +974,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
                sql_result_error(context, err_msg, -1);
                return;
        }
-       sql_result_boolean(context, res == MATCH);
+       sql_result_bool(context, res == MATCH);
 }
 
 /*
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index a74fa4b9e..7dbca2a63 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -499,7 +499,7 @@ void
 sql_result_int(sql_context *, int);
 
 void
-sql_result_boolean(sql_context *ctx, bool value);
+sql_result_bool(sql_context *ctx, bool value);
 
 void
 sql_result_int64(sql_context *, sql_int64);
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 8e161c00c..7951ba91a 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -497,7 +497,7 @@ int sqlVdbeMemSetStr(Mem *, const char *, int, u8, void (*)(void *));
 void sqlVdbeMemSetInt64(Mem *, i64);
 
 void
-vdbe_mem_set_boolean(struct Mem *mem, bool value);
+vdbe_mem_set_bool(struct Mem *mem, bool value);
 
 #ifdef SQL_OMIT_FLOATING_POINT
 #define sqlVdbeMemSetDouble sqlVdbeMemSetInt64
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 18f7587c3..e4dad3584 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -387,9 +387,9 @@ sql_result_int(sql_context * pCtx, int iVal)
 }
 
 void
-sql_result_boolean(sql_context *ctx, bool value)
+sql_result_bool(sql_context *ctx, bool value)
 {
-       vdbe_mem_set_boolean(ctx->pOut, value);
+       vdbe_mem_set_bool(ctx->pOut, value);
 }
 
 void
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 1590a3550..69aea5ef5 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -803,7 +803,7 @@ sqlVdbeMemSetInt64(Mem * pMem, i64 val)
 }
 
 void
-vdbe_mem_set_boolean(struct Mem *mem, bool value)
+vdbe_mem_set_bool(struct Mem *mem, bool value)
 {

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

* [tarantool-patches] Re: [PATCH 9/9] sql: make <search condition> accept only boolean
  2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-04-18 17:55     ` n.pettik
  2019-04-22 18:02       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 42+ messages in thread
From: n.pettik @ 2019-04-18 17:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>> <search condition> is a predicate used as a part of WHERE and
>> JOIN clauses. ANSI SQL states that <search condition> must
>> accept only boolean arguments. In our SQL it is implemented as
>> bytecode instruction OP_If which in turn carries out logic of
>> conditional jump. Since it can be involved in executing other routines
>> different from <search condition>, 
> 
> 1. Which other routines? What is a valid case of OP_If with non-boolean
> value in check?

For instance, to verify that register containing LIMIT value is > 0.
It is quite hard to track values which come to this opcode, so we
can’t be sure that it always accepts booleans.

>> we pass to it additional argument
>> when generating bytecode for WHERE and JOIN clauses. When VDBE performs
>> OP_If and detects such flag, it checks passed argument to be boolean.
>> 
>> Closes #3723
> 
> 2. In addition, it fixes https://github.com/tarantool/tarantool/issues/3651,
> doesn't it?

It does, added label.

>> diff --git a/test/sql-tap/e_delete.test.lua b/test/sql-tap/e_delete.test.lua
>> index a58dc87c7..374a7d3e4 100755
>> --- a/test/sql-tap/e_delete.test.lua
>> +++ b/test/sql-tap/e_delete.test.lua
>> @@ -89,15 +89,15 @@ test:do_delete_tests("e_delete-1.1", {
>> -- NULL are retained.
>> --
>> test:do_delete_tests("e_delete-1.2", {
>> -    {1, "DELETE FROM t3 WHERE 1       ; SELECT x FROM t3", {}},
>> -    {2, "DELETE FROM t4 WHERE 0  ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
>> -    {3, "DELETE FROM t4 WHERE 0.0     ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
>> +    {1, "DELETE FROM t3 WHERE true       ; SELECT x FROM t3", {}},
>> +    {2, "DELETE FROM t4 WHERE false  ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
>> +    {3, "DELETE FROM t4 WHERE false    ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
> 
> 3. The last two lines are exactly the same. Why not to drop one?

Ok, dropped.

>>     {4, "DELETE FROM t4 WHERE NULL    ; SELECT x FROM t4", {1, 2, 3, 4, 5}},
>>     {5, "DELETE FROM t4 WHERE y!='two'; SELECT x FROM t4", {2}},
>>     {6, "DELETE FROM t4 WHERE y='two' ; SELECT x FROM t4", {}},
>>     {7, "DELETE FROM t5 WHERE x=(SELECT max(x) FROM t5);SELECT x FROM t5", {1, 2, 3, 4}},
>>     {8, "DELETE FROM t5 WHERE (SELECT max(x) FROM t4)  ;SELECT x FROM t5", {1, 2, 3, 4}},
>> -    {9, "DELETE FROM t5 WHERE (SELECT max(x) FROM t6)  ;SELECT x FROM t5", {}},
>> +    {9, "DELETE FROM t5 WHERE (SELECT max(x) FROM t6) != 0  ;SELECT x FROM t5", {}},
>>     {10, "DELETE FROM t6 WHERE y>'seven' ; SELECT y FROM t6", {"one", "four", "five"}},
>> })
>> 
>> diff --git a/test/sql-tap/e_select1.test.lua b/test/sql-tap/e_select1.test.lua
>> index 970eeeed9..e47b0f43d 100755
>> --- a/test/sql-tap/e_select1.test.lua
>> +++ b/test/sql-tap/e_select1.test.lua
>> @@ -448,15 +448,15 @@ test:do_select_tests(
>> -- true are included from the dataset.
>> --
>> local data ={
>> -    {"1"," SELECT * FROM t1 JOIN_PATTERN t2 ON (1) ",t1_cross_t2},
>> -    {"2"," SELECT * FROM t1 JOIN_PATTERN t2 ON (0) ",{}},
>> +    {"1"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
>> +    {"2"," SELECT * FROM t1 JOIN_PATTERN t2 ON (false) ",{}},
>>     {"3"," SELECT * FROM t1 JOIN_PATTERN t2 ON (NULL) ",{}},
>> -    {"6"," SELECT * FROM t1 JOIN_PATTERN t2 ON (0.9) ",t1_cross_t2},
>> -    {"7"," SELECT * FROM t1 JOIN_PATTERN t2 ON ('0.9') ",t1_cross_t2},
>> -    {"8"," SELECT * FROM t1 JOIN_PATTERN t2 ON (0.0) ",{}},
>> +    {"6"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
>> +    {"7"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
>> +    {"8"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
> 
> 4. The same. 3 duplicates.

Removed duplicates.

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

* [tarantool-patches] Re: [PATCH 9/9] sql: make <search condition> accept only boolean
  2019-04-18 17:55     ` n.pettik
@ 2019-04-22 18:02       ` Vladislav Shpilevoy
  2019-04-23 19:59         ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-22 18:02 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Thanks for the fixes!

On 18/04/2019 20:55, n.pettik wrote:
> 
>>> <search condition> is a predicate used as a part of WHERE and
>>> JOIN clauses. ANSI SQL states that <search condition> must
>>> accept only boolean arguments. In our SQL it is implemented as
>>> bytecode instruction OP_If which in turn carries out logic of
>>> conditional jump. Since it can be involved in executing other routines
>>> different from <search condition>, 
>>
>> 1. Which other routines? What is a valid case of OP_If with non-boolean
>> value in check?
> 
> For instance, to verify that register containing LIMIT value is > 0.

Yes, and this is almost the only case. What is more, it happens only once
per request, to check if LIMIT == 0 initially. Further it is decremented
and checked via OP_IfNotZero and OP_DecrJumpZero.

> It is quite hard to track values which come to this opcode, so we
> can’t be sure that it always accepts booleans.

It is hard, but without it

1) You can't be sure, that really all the search conditions
   are checked to be booleans;

2) It makes OP_If/IfNot slower, and they are called repeatedly in
   requests;

3) It adds one more flag SQL_BOOLREQ, which looks very crutchy.

I tried myself to disable non-bool for OP_If/IfNot, and I fixed
LIMIT cases. Then I've found CASE-WHEN also uses OP_If/IfNot in
this distorted way.

tarantool> box.execute("SELECT CASE WHEN 0 THEN 'zero' WHEN 1 THEN 'one' END;")
---
- metadata:
  - name: CASE WHEN 0 THEN 'zero' WHEN 1 THEN 'one' END
    type: string
  rows:
  - ['one']
...

It violates the standard. "Information technology —
Database languages — SQL — Part 2: Foundation (SQL/Foundation)",
2011, page 230.

<case expression> ::= <case abbreviation> | <case specification>

<case abbreviation> ::=
    NULLIF <left paren> <value expression> <comma> <value expression> <right paren>
  | COALESCE <left paren> <value expression>
        { <comma> <value expression> }... <right paren>

<case specification> ::= <simple case> | <searched case>

<simple case> ::= CASE <case operand> <simple when clause>... [ <else clause> ] END

<searched case> ::= CASE <searched when clause>... [ <else clause> ] END

<simple when clause> ::= WHEN <when operand list> THEN <result>

<searched when clause> ::= WHEN <search condition> THEN <result>

Our case is 'case expression' -> 'case specification' ->
            'searched case' -> 'searched when clause' ->
            'search condition' -> 'boolean'.

'WHEN' is a search condition, but I've used '1', not 'true'.
Also I tested it on PostgreSQL - they raise an error, so it is
both standard and practically used way.



Below are my fixes for LIMIT and a small obvious refactoring,
but they are *not on the branch* - not all the tests pass when I
start banning non-bools in OP_If/IfNot. And I do not like these
temporary registers. Probably we could somehow reuse OP_IfNotZero,
for example, via having it refactored to OP_CheckZero which would
be able to jump both if the target is zero and if not. For example,
it could look like this:

    /**
     * P1 - a register with value to check if it equals zero.
     * P3 - if 1, then check r[P1] == 0, if 0, then check r[P1] != 0.
     * If the check is successful, then jump to P2.
     * 
     * if (r[P1] == 0) == P3 then jump p2
     */

But I do not know what to do with its current version decrementing
r[P1]. My ideas were about splitting 'decrement' operation from
zero-checking. Then we could do these replacements:

    OP_IfNotZero -> OP_CheckZero, OP_Decrement

    OP_DecrJumpZero -> OP_Decrement, OP_CheckZero

    OP_IfNot -> OP_CheckZero (where it is used inappropriately)

And now we drop OP_IfNotZero and OP_DecrJumpZero, and forbid
non-bool in OP_IfNot/OP_If. Perhaps we will find more standard
violations.

==================================================================
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 3c4cde2c8..86fc28606 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -1094,24 +1094,16 @@ sqlPExprAddSelect(Parse * pParse, Expr * pExpr, Select * pSelect)
  * LEFT JOIN, then we cannot determine at compile-time whether or not
  * is it true or false, so always return 0.
  */
-static int
+static inline bool
 exprAlwaysTrue(Expr * p)
 {
-	if (ExprHasProperty(p, EP_FromJoin))
-		return 0;
-	if (p->op == TK_TRUE)
-		return 1;
-	return 0;
+	return !ExprHasProperty(p, EP_FromJoin) && p->op == TK_TRUE;
 }
 
-static int
+static inline bool
 exprAlwaysFalse(Expr * p)
 {
-	if (ExprHasProperty(p, EP_FromJoin))
-		return 0;
-	if (p->op == TK_FALSE)
-		return 1;
-	return 0;
+	return !ExprHasProperty(p, EP_FromJoin) && p->op == TK_FALSE;
 }
 
 struct Expr *
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 428e3a92f..3680b6f2b 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -769,8 +769,10 @@ pushOntoSorter(Parse * pParse,		/* Parser context */
 		pParse->nMem += pSort->nOBSat;
 		nKey = nExpr - pSort->nOBSat + bSeq;
 		if (bSeq) {
-			addrFirst =
-			    sqlVdbeAddOp1(v, OP_IfNot, regBase + nExpr);
+			int r1 = sqlGetTempReg(pParse);
+			sqlVdbeAddOp2(v, OP_Integer, 0, r1);
+			addrFirst = sqlVdbeAddOp3(v, OP_Eq, r1, 0, regBase + nExpr);
+			sqlReleaseTempReg(pParse, r1);
 		} else {
 			addrFirst =
 			    sqlVdbeAddOp1(v, OP_SequenceTest,
@@ -799,8 +801,10 @@ pushOntoSorter(Parse * pParse,		/* Parser context */
 				  pSort->labelBkOut);
 		sqlVdbeAddOp1(v, OP_ResetSorter, pSort->iECursor);
 		if (iLimit) {
-			sqlVdbeAddOp2(v, OP_IfNot, iLimit,
-					  pSort->labelDone);
+			int r1 = sqlGetTempReg(pParse);
+			sqlVdbeAddOp2(v, OP_Integer, 0, r1);
+			sqlVdbeAddOp3(v, OP_Eq, r1, pSort->labelDone, iLimit);
+			sqlReleaseTempReg(pParse, r1);
 			VdbeCoverage(v);
 		}
 		sqlVdbeJumpHere(v, addrFirst);
@@ -2115,10 +2119,10 @@ computeLimitRegisters(Parse * pParse, Select * p, int iBreak)
 				  P4_STATIC);
 
 		sqlVdbeResolveLabel(v, positive_limit_label);
-		sqlReleaseTempReg(pParse, r1);
 		VdbeCoverage(v);
 		VdbeComment((v, "LIMIT counter"));
-		sqlVdbeAddOp2(v, OP_IfNot, iLimit, iBreak);
+		sqlVdbeAddOp3(v, OP_Eq, r1, iBreak, iLimit);
+		sqlReleaseTempReg(pParse, r1);
 		VdbeCoverage(v);
 
 		if ((p->selFlags & SF_SingleRow) != 0) {
@@ -2673,10 +2677,11 @@ multiSelect(Parse * pParse,	/* Parsing context */
 				p->iLimit = pPrior->iLimit;
 				p->iOffset = pPrior->iOffset;
 				if (p->iLimit) {
-					addr =
-					    sqlVdbeAddOp1(v, OP_IfNot,
-							      p->iLimit);
+					int r1 = sqlGetTempReg(pParse);
+					sqlVdbeAddOp2(v, OP_Integer, 0, r1);
+					addr = sqlVdbeAddOp3(v, OP_Eq, r1, 0, p->iLimit);
 					VdbeCoverage(v);
+					sqlReleaseTempReg(pParse, r1);
 					VdbeComment((v,
 						     "Jump ahead if LIMIT reached"));
 					if (p->iOffset) {

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

* [tarantool-patches] Re: [PATCH 8/9] sql: make LIKE predicate return boolean result
  2019-04-18 17:55     ` n.pettik
@ 2019-04-22 18:02       ` Vladislav Shpilevoy
  2019-04-23 19:58         ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-22 18:02 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Thanks for the fixes!

> diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
> index 1590a3550..69aea5ef5 100644
> --- a/src/box/sql/vdbemem.c
> +++ b/src/box/sql/vdbemem.c
> @@ -803,7 +803,7 @@ sqlVdbeMemSetInt64(Mem * pMem, i64 val)
>  }
>  
>  void
> -vdbe_mem_set_boolean(struct Mem *mem, bool value)
> +vdbe_mem_set_bool(struct Mem *mem, bool value)
>  {
> 

1. We now have mem_value_bool(), and so as to be consistent
it would be better to name this function mem_set_bool(), not
vdbe_mem_set_bool(). It does not take struct Vdbe pointer
anyway.

2. Is there any concrete reason why vdbe_mem_set_bool() is
not used now in the following places?

vdbe.c:1101
	pOut = out2Prerelease(p, pOp);
	assert(pOp->p1 == 1 || pOp->p1 == 0);
	pOut->flags = MEM_Bool;
	pOut->u.b = pOp->p1;
	break;

vdbe.c:2275
	memAboutToChange(p, pOut);
	MemSetTypeFlag(pOut, MEM_Bool);
	pOut->u.b = res2;
	REGISTER_TRACE(pOp->p2, pOut);

vdbe.c:2471
	pOut->u.b = v1;
	MemSetTypeFlag(pOut, MEM_Bool);

vdbe.c:2495
	pOut->flags = MEM_Bool;
	pOut->u.b = ! pIn1->u.b;

If it works for them, then I propose to move vdbe_mem_set_bool()
to the patch 'sql: introduce type boolean', and use it.

Also, please, consider this minor review fix, on the branch:

==================================================================
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 8426f9b5f..07c887bb4 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -499,7 +499,7 @@ void
 sql_result_int(sql_context *, int);
 
 void
-sql_result_bool(sql_context *ctx, bool value);
+sql_result_bool(struct sql_context *ctx, bool value);
 
 void
 sql_result_int64(sql_context *, sql_int64);
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index c2a319dbd..9a405c8ee 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -387,7 +387,7 @@ sql_result_int(sql_context * pCtx, int iVal)
 }
 
 void
-sql_result_bool(sql_context *ctx, bool value)
+sql_result_bool(struct sql_context *ctx, bool value)
 {
 	vdbe_mem_set_bool(ctx->pOut, value);
 }

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

* [tarantool-patches] Re: [PATCH 5/9] sql: improve type determination for column meta
  2019-04-18 17:54     ` n.pettik
@ 2019-04-22 18:02       ` Vladislav Shpilevoy
  2019-04-23 19:58         ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-22 18:02 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Thanks for the fixes!

On 18/04/2019 20:54, n.pettik wrote:
> 
> 
>> On 16 Apr 2019, at 17:12, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>>
>> On 14/04/2019 18:04, Nikita Pettik wrote:
>>> In most cases we don't assign and store type of node of expression AST
>>> (except for constant literals).  To determine type of node  we use
>>> sql_expr_type() function, which implements logic of quite simple
>>> recursive tree traversal. Before this patch we set type of node after
>>> code generation in sqlExprCodeTarget() without any traversal. This
>>> approach is way worse even then sql_expr_type().
>>
>> Why? How is recursive scan of the tree better than simple access to
>> a struct's attribute? I see, that your patch improves type determination
>> precision, but I do not understand how, and what a perf price for that?
> 
> Firstly, it seems that sqlExprCodeTarget() can be called on copy of
> original expr (when query contains order/group by clauses): I see
> that types assigned in that function are different from types in expr
> passed to generateColumnNames().

Ok, now I got it - the original expr still is unchanged, and types
resolving in codetarget() is thrown into a bucket.

> 
> Secondly, suggested approach simply makes code cleaner and easier
> to maintain: we shouldn’t care about adding on each branch of switch
> appropriate type (since it is already done in sql_expr_type()).
> 
> Eventually, sqlExprCodeTarget() doesn’t take into consideration
> types of child nodes. This drawback will appear when we extend
> integer type with small/big ints:
> 
> Column a is of type smallint.
> 
> SELECT 1 + a;
> 
> Should result in ‘smallint' type, not common ‘number’.
> Without tree traversal we can’t do that.
> 
> Price is a primitive traversal through the expr tree. This traversal (but in more
> sophisticated implementation and being called only once saving in each node
> its type) is anyway required to implement this task:
> https://github.com/tarantool/tarantool/issues/3103 <https://github.com/tarantool/tarantool/issues/3103>

I do not have anything against traversal. I just don't like multiple
traversal and seemingly free and fast call of deceptive sql_expr_type() -
it is expensive in fact, just look at this huge 'switch-case'.

Your patch is ok, it does not aggravate anything. But sql_expr_type()
is called in very many places. Probably, in scope of 3103 we should
set Expr type always before optimizer and Vdbe-coder are started. Just
thoughts.

> 
> In fact, we should once visit each node (probably after names resolution)
> and save its type depending on child’s types.
> 
>> If you are not sure about the price, we could ask Alexander to run
>> benches on your branch before pushing into the master.
> 
> This patch is auxiliary to main-patchset. I’ve added it to avoid
> abusing changing result files like sql/types.result etc. Can drop it tho.

Now I see, that there is nothing to bench. Almost pure refactoring.

>>> So, to improve accuracy
>>> of type determination, let's always call that method and remove type
>>> assignment in sqlExprCodeTarget().
>>
>> This patch fixes the issue
>> https://github.com/tarantool/tarantool/issues/4126.
> 
> Ok, added label.
> 

During the second review I found some orphan type assignments
in codetarget(). I dropped them and the tests passed. The diff is
on the branch.

==================================================================
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 016d6f899..0ad4b74f8 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -3725,7 +3725,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 	case TK_AGG_COLUMN:{
 			AggInfo *pAggInfo = pExpr->pAggInfo;
 			struct AggInfo_col *pCol = &pAggInfo->aCol[pExpr->iAgg];
-			pExpr->type = pCol->pExpr->type;
 			if (!pAggInfo->directMode) {
 				assert(pCol->iMem > 0);
 				return pCol->iMem;
@@ -3757,7 +3756,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 					iTab = pParse->iSelfTab;
 				}
 			}
-			pExpr->type = pExpr->space_def->fields[col].type;
 			return sqlExprCodeGetColumn(pParse,
 							pExpr->space_def, col,
 							iTab, target,
@@ -3925,7 +3923,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 		}
 	case TK_UMINUS:{
 			Expr *pLeft = pExpr->pLeft;
-			pExpr->type = FIELD_TYPE_NUMBER;
 			assert(pLeft);
 			if (pLeft->op == TK_INTEGER) {
 				expr_code_int(pParse, pLeft, true, target);
@@ -3989,7 +3986,6 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 					 tt_sprintf(err, pExpr->u.zToken));
 				pParse->is_aborted = true;
 			} else {
-				pExpr->type = pInfo->aFunc->pFunc->ret_type;
 				return pInfo->aFunc[pExpr->iAgg].iMem;
 			}
 			break;

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

* [tarantool-patches] Re: [PATCH 4/9] sql: introduce type boolean
  2019-04-18 17:54     ` n.pettik
@ 2019-04-22 18:02       ` Vladislav Shpilevoy
  2019-04-23 19:58         ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-22 18:02 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Hi! Thanks for the fixes! See 3 comments below.

> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index 6b38e8e66..d70b64c45 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -3775,7 +3766,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
>                 }
>         case TK_TRUE:
>         case TK_FALSE: {
> -                       vdbe_emit_bool(pParse->pVdbe, pExpr, target);
> +                       sqlVdbeAddOp2(v, OP_Bool, op == TK_TRUE, target);
>                         return target;
>                 }
> 
>> 6. I see, that all other 'case's set pExpr->type. Why don't you
>> do it here?
> 
> It is required only for non-constant values. For literals it is assigned
> in parse.y spanExpr().

1. Then why case TK_INTEGER sets 'pExpr->type = FIELD_TYPE_INTEGER',
TK_STRING sets 'pExpr->type = FIELD_TYPE_STRING;', TK_BLOB sets
'pExpr->type = FIELD_TYPE_SCALAR;' - I see all of them being set in
parse.y.

On the contrary, there are places which create a new Expr, and
does not set type even in parse.y. For example:

    1240: A.pExpr = sql_expr_new_dequoted(pParse->db, TK_INTEGER, &sqlIntTokens[N]);

Here the expr is created and spanExpr() is not used. How does it
work?

>> 8. Looking at all these type comparison prohibitions I am getting
>> afraid of how will we select from SCALAR columns?
>>
>> A schema:
>>
>>    box.execute('CREATE TABLE t (id INTEGER PRIMARY KEY, a SCALAR UNIQUE);')
>>    box.execute("INSERT INTO t VALUES (1, 1), (2, true), (3, false), (4, 'str')")
>>
>> SQL select:
>>
>>> box.execute('SELECT * FROM t WHERE a > 1')
>>    ---
>>    - error: 'Type mismatch: can not convert INTEGER to boolean'
>>    ...
>>
>> The same Lua select:
>>
>>> box.space.T.index[1]:select({1}, {iterator = 'GT'})
>>    ---
>>    - - [4, 'str']
>>    ...
>>
>> In fact, now we can not use SCALAR in SQL for any comparisons
>> because it will raise type mismatch on literally everything.
>>
>> What are we going to do with it? IMO, we could add a flag like
>> 'is_scalar' to struct Mem in its flags section, which would allow
>> to compare this value with anything.
> 
> Konstantin suggested to extend struct Mem with field_type,
> which is going to substitute this flag.
> 
>> Looks crutchy though. And is
>> not a part of this issue of course, but should be filed into the
>> issue list.
>>
>> I see a similar issue https://github.com/tarantool/tarantool/issues/4124.
>> Probably, it is worth extending it instead of opening new.
> 
> I consider this as a trade-off of using scalar: users shouldn’t use this
> type until they are really have to. Nevertheless, they still can use CAST
> operator and switch case, or write their own lua-function converting values
> to required type.

2. Users may have good designed schema and work in each request with only
one type in such a column, but still they will get errors just because
the iterator can not walk to the needed tree node. Lets modify my example:

    SELECT * FROM t WHERE a = 1;

This request implicitly says that it will work with numbers only, but
somehow its success depends on the content of the space, not even
related to '1' value. In a nutshell it means that insertion of something
into a space can break existing requests not related to the inserted
data. It is weird. Such scalar type would be useless.

>>> + *
>>> + * @param str String to be converted to boolean.
>>> + *            Assumed to be null terminated.
>>> + * @param result Resulting value of cast.
>>> + * @retval 0 If string satisfies conditions above.
>>> + * @retval -1 Otherwise.
>>> + */
>>> +static int
>>> +str_cast_to_boolean(const char *str, bool *result)
>>> +{
>>> +	assert(str != NULL);
>>> +	for (; *str == ' '; str++);
>>> +	size_t rest_str_len = strlen(str);
>>> +	if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
>>> +		*result = true;
>>> +		return 0;
>>> +	}
>>> +	if (rest_str_len == 5 && strncasecmp(str, "false", 5) == 0) {
>>> +		*result = false;
>>> +		return 0;
>>> +	}
>>
>> 16. I guess it is far from the most efficient implementation. You
>> calculate length of the whole string before comparison, scanning
>> its twice in the worst and most common case. Please, consider my
>> fixes below. I did not test them though, so probably there are some
>> typos.
>> Note, that in my fixes I've already accounted my comment about
>> trimming trailing whitespaces.
>>
>> ============================================================================
>> @@ -625,16 +625,20 @@ str_cast_to_boolean(const char *str, bool *result)
>> {
>> 	assert(str != NULL);
>> 	for (; *str == ' '; str++);
>> -	size_t rest_str_len = strlen(str);
>> -	if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
>> +	if (strncasecmp(str, "true", 4) == 0) {
> 
> We can’t use strncasecmp() without verification the fact that
> rest_str_len (i.e. string length after skipping leading spaces) is >= 4.

3.1. Why? According to the standard, strncasecmp() compares not more than
'n' characters taking into account terminating zero. I tried these
examples to simulate our case (when 'str' points to the beginning of a
possible 'true/false' token).

	printf("%d\n", strncasecmp("", "true", 4));
	printf("%d\n", strncasecmp("t", "true", 4));
	printf("%d\n", strncasecmp("tr", "true", 4));
	printf("%d\n", strncasecmp("tru", "true", 4));
	printf("%d\n", strncasecmp("true   ", "true", 4));

I got this output:

    -116
    -114
    -117
    -101
    0

It means, that you do not need to call 'strlen' before
'strncasecmp'. I've dropped 'strlen' again and the tests
passed. If you think that explicit length check is mandatory,
then please, provide a test. Probably I'm wrong somewhere in
my speculations.

> 
>> +		str += 4;
>> 		*result = true;
>> -		return 0;
>> -	}
>> -	if (rest_str_len == 5 && strncasecmp(str, "false", 5) == 0) {
>> +	} else if (strncasecmp(str, "false", 5) == 0) {
>> +		str += 5;
>> 		*result = false;
>> -		return 0;
>> +	} else {
>> +		return -1;
>> 	}
>> -	return -1;
>> +	for (; *str != 0; ++str) {
>> +		if (*str != ' ')
>> +			return -1;
>> +	}
>> +	return 0;
>> }
>> ============================================================================
> 
> Taking into account my comment diff is next:
> 
> diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
> index 92406f938..19b42b992 100644
> --- a/src/box/sql/vdbemem.c
> +++ b/src/box/sql/vdbemem.c
> @@ -626,15 +626,20 @@ str_cast_to_boolean(const char *str, bool *result)
>         assert(str != NULL);
>         for (; *str == ' '; str++);
>         size_t rest_str_len = strlen(str);

3.2. My main point was that we should not call
strlen() - it leads to double scan of the string. If you
still need strlen(), then the fixes are useless. But consider
my objectives above, about the POSIX standard.

> -       if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
> +       if (rest_str_len >= 4 && strncasecmp(str, "true", 4) == 0) {
>                 *result = true;
> -               return 0;
> -       }
> -       if (rest_str_len == 5 && strncasecmp(str, "false", 5) == 0) {
> +               str += 4;
> +       } else if (rest_str_len >= 5 && strncasecmp(str, "false", 5) == 0) {
>                 *result = false;
> -               return 0;
> +               str += 5;
> +       } else {
> +               return -1;
>         }
> -       return -1;
> +       for (; *str != '\0'; ++str) {
> +               if (*str != ' ')
> +                       return -1;
> +       }
> +       return 0;
>  }

My fixes, on the branch:

==================================================================
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 08bea9d75..dbbb7fe23 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1102,8 +1102,8 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 			break;
 		}
 	case MP_BOOL: {
-		sql_result_text(context, argv[0]->u.b ? "true" : "false", -1,
-				SQL_TRANSIENT);
+		sql_result_text(context, sql_value_boolean(argv[0]) ?
+				"true" : "false", -1, SQL_TRANSIENT);
 		break;
 	}
 	default:{
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 468c89521..1f77d34cd 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -583,7 +583,7 @@ int
 sql_column_int(sql_stmt *, int iCol);
 
 bool
-sql_column_boolean(sql_stmt *stmt, int column);
+sql_column_boolean(struct sql_stmt *stmt, int column);
 
 sql_int64
 sql_column_int64(sql_stmt *, int iCol);
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 73c77efed..db8458a6a 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -510,7 +510,7 @@ int sqlVdbeMemIntegerify(Mem *, bool is_forced);
 int sqlVdbeRealValue(Mem *, double *);
 
 int
-mem_value_bool(Mem *mem, bool *b);
+mem_value_bool(const struct Mem *mem, bool *b);
 
 int mem_apply_integer_type(Mem *);
 int sqlVdbeMemRealify(Mem *);
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index cfd65075e..9bbe6d2ad 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -991,7 +991,7 @@ sql_column_int(sql_stmt * pStmt, int i)
 }
 
 bool
-sql_column_boolean(sql_stmt *stmt, int i)
+sql_column_boolean(struct sql_stmt *stmt, int i)
 {
 	bool val = sql_value_boolean(columnMem(stmt, i));
 	columnMallocFailure(stmt);
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 19b42b992..0bc1c9466 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -502,9 +502,9 @@ sqlVdbeRealValue(Mem * pMem, double *v)
 }
 
 int
-mem_value_bool(Mem *mem, bool *b)
+mem_value_bool(const struct Mem *mem, bool *b)
 {
-	if (mem->flags  & MEM_Bool) {
+	if ((mem->flags  & MEM_Bool) != 0) {
 		*b = mem->u.b;
 		return 0;
 	}
@@ -614,9 +614,9 @@ sqlVdbeMemNumerify(Mem * pMem)
  * For instance, "   tRuE  " can be successfully converted to
  * boolean value true.
  *
- * @param str String to be converted to boolean.
- *            Assumed to be null terminated.
- * @param result Resulting value of cast.
+ * @param str String to be converted to boolean. Assumed to be
+ *        null terminated.
+ * @param[out] result Resulting value of cast.
  * @retval 0 If string satisfies conditions above.
  * @retval -1 Otherwise.
  */
@@ -625,11 +625,10 @@ str_cast_to_boolean(const char *str, bool *result)
 {
 	assert(str != NULL);
 	for (; *str == ' '; str++);
-	size_t rest_str_len = strlen(str);
-	if (rest_str_len >= 4 && strncasecmp(str, "true", 4) == 0) {
+	if (strncasecmp(str, "true", 4) == 0) {
 		*result = true;
 		str += 4;
-	} else if (rest_str_len >= 5 && strncasecmp(str, "false", 5) == 0) {
+	} else if (strncasecmp(str, "false", 5) == 0) {
 		*result = false;
 		str += 5;
 	} else {
@@ -680,7 +679,7 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type)
 			pMem->u.b = value;
 			return 0;
 		}
-		if ((pMem->flags & MEM_Bool)  != 0)
+		if ((pMem->flags & MEM_Bool) != 0)
 			return 0;
 		return -1;
 	case FIELD_TYPE_INTEGER:

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

* [tarantool-patches] Re: [PATCH 3/9] sql: use msgpack types instead of custom ones
  2019-04-18 17:54     ` n.pettik
@ 2019-04-22 18:02       ` Vladislav Shpilevoy
  2019-04-23 19:58         ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-22 18:02 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Thanks for the fixes!

>>> @@ -139,15 +139,15 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
>>>
>>> 	assert(argc == 1);
>>> 	UNUSED_PARAMETER(argc);
>>> -	switch (sql_value_type(argv[0])) {
>>> -	case SQL_BLOB:
>>> -	case SQL_INTEGER:
>>> -	case SQL_FLOAT:{
>>> +	switch (sql_value_mp_type(argv[0])) {
>>> +	case MP_BIN:
>>> +	case MP_INT:
>>> +	case MP_DOUBLE:{
>>
>> 4. Probably you could fix part of this:
>> https://github.com/tarantool/tarantool/issues/4159 in scope of
>> this commit, alongside.
> 
> THis patch provides straightforward refactoring, I don’t want
> to involve here non-trivial changes.

Yes, you are right, it is not worth doing here.

> diff --git a/src/box/sql/date.c b/src/box/sql/date.c
> index 5f5272ea3..1c7a5ad2d 100644
> --- a/src/box/sql/date.c
> +++ b/src/box/sql/date.c
> @@ -932,12 +932,12 @@ isDate(sql_context * context, int argc, sql_value ** argv, DateTime * p)
>  {
>  	int i, n;
>  	const unsigned char *z;
> -	int eType;
> +	enum mp_type eType;
>  	memset(p, 0, sizeof(*p));
>  	if (argc == 0) {
>  		return setDateTimeToCurrent(context, p);
>  	}
> -	if ((eType = sql_value_type(argv[0])) == SQL_FLOAT
> +	if ((eType = sql_value_type(argv[0])) == MP_DOUBLE
>  	    || eType == SQL_INTEGER) {

SQL_INTEGER is removed already. Please, replace with MP_INT.

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

* [tarantool-patches] Re: [PATCH 2/9] sql: disallow text values participate in sum() aggregate
  2019-04-18 17:54     ` n.pettik
@ 2019-04-22 18:02       ` Vladislav Shpilevoy
  2019-04-23 19:58         ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-22 18:02 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Hi! Thanks for the fixes!

> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index b86a95d9a..9adfeec67 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -1495,24 +1495,34 @@ static void
>  sumStep(sql_context * context, int argc, sql_value ** argv)
>  {
>  	SumCtx *p;
> -	int type;
>  	assert(argc == 1);
>  	UNUSED_PARAMETER(argc);
>  	p = sql_aggregate_context(context, sizeof(*p));
> -	type = sql_value_numeric_type(argv[0]);
> -	if (p && type != SQL_NULL) {
> -		p->cnt++;
> -		if (type == SQL_INTEGER) {
> -			i64 v = sql_value_int64(argv[0]);
> -			p->rSum += v;
> -			if ((p->approx | p->overflow) == 0
> -			    && sqlAddInt64(&p->iSum, v)) {
> -				p->overflow = 1;
> -			}
> -		} else {
> -			p->rSum += sql_value_double(argv[0]);
> -			p->approx = 1;
> +	assert(p != NULL);

Why are you sure, that p != NULL? sql_aggregate_context()
on first invocation allocates memory, and it can fail.

> +	int type = sql_value_type(argv[0]);
> +	if (type == SQL_NULL)
> +		return;

I've fixed the comment above and also I see, that sumStep is
rewritten almost completely, so it is time to convert it to
Tarantool style. See the diff below and on the branch in a
separate commit.

(Note, 'sql_value' below is not prepended with 'struct' because it
is a typedef - compiler curses).

==================================================================
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 9adfeec67..0f9e228dc 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1492,15 +1492,13 @@ struct SumCtx {
  * it overflows an integer.
  */
 static void
-sumStep(sql_context * context, int argc, sql_value ** argv)
+sum_step(struct sql_context *context, int argc, sql_value **argv)
 {
-	SumCtx *p;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	p = sql_aggregate_context(context, sizeof(*p));
-	assert(p != NULL);
+	struct SumCtx *p = sql_aggregate_context(context, sizeof(*p));
 	int type = sql_value_type(argv[0]);
-	if (type == SQL_NULL)
+	if (type == SQL_NULL || p == NULL)
 		return;
 	if (type != SQL_FLOAT && type != SQL_INTEGER) {
 		if (mem_apply_numeric_type(argv[0]) != 0) {
@@ -1514,10 +1512,10 @@ sumStep(sql_context * context, int argc, sql_value ** argv)
 	}
 	p->cnt++;
 	if (type == SQL_INTEGER) {
-		i64 v = sql_value_int64(argv[0]);
+		int64_t v = sql_value_int64(argv[0]);
 		p->rSum += v;
 		if ((p->approx | p->overflow) == 0 &&
-		    sqlAddInt64(&p->iSum, v)) {
+		    sqlAddInt64(&p->iSum, v) != 0) {
 			p->overflow = 1;
 		}
 	} else {
@@ -1870,9 +1868,12 @@ sqlRegisterBuiltinFunctions(void)
 		FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc, FIELD_TYPE_SCALAR),
 		FUNCTION_COLL(substr, 2, 0, 0, substrFunc),
 		FUNCTION_COLL(substr, 3, 0, 0, substrFunc),
-		AGGREGATE(sum, 1, 0, 0, sumStep, sumFinalize, FIELD_TYPE_NUMBER),
-		AGGREGATE(total, 1, 0, 0, sumStep, totalFinalize, FIELD_TYPE_NUMBER),
-		AGGREGATE(avg, 1, 0, 0, sumStep, avgFinalize, FIELD_TYPE_NUMBER),
+		AGGREGATE(sum, 1, 0, 0, sum_step, sumFinalize,
+			  FIELD_TYPE_NUMBER),
+		AGGREGATE(total, 1, 0, 0, sum_step, totalFinalize,
+			  FIELD_TYPE_NUMBER),
+		AGGREGATE(avg, 1, 0, 0, sum_step, avgFinalize,
+			  FIELD_TYPE_NUMBER),
 		AGGREGATE2(count, 0, 0, 0, countStep, countFinalize,
 			   SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
 		AGGREGATE(count, 1, 0, 0, countStep, countFinalize,

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

* [tarantool-patches] Re: [PATCH 2/9] sql: disallow text values participate in sum() aggregate
  2019-04-22 18:02       ` Vladislav Shpilevoy
@ 2019-04-23 19:58         ` n.pettik
  0 siblings, 0 replies; 42+ messages in thread
From: n.pettik @ 2019-04-23 19:58 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index b86a95d9a..9adfeec67 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -1495,24 +1495,34 @@ static void
>> sumStep(sql_context * context, int argc, sql_value ** argv)
>> {
>> 	SumCtx *p;
>> -	int type;
>> 	assert(argc == 1);
>> 	UNUSED_PARAMETER(argc);
>> 	p = sql_aggregate_context(context, sizeof(*p));
>> -	type = sql_value_numeric_type(argv[0]);
>> -	if (p && type != SQL_NULL) {
>> -		p->cnt++;
>> -		if (type == SQL_INTEGER) {
>> -			i64 v = sql_value_int64(argv[0]);
>> -			p->rSum += v;
>> -			if ((p->approx | p->overflow) == 0
>> -			    && sqlAddInt64(&p->iSum, v)) {
>> -				p->overflow = 1;
>> -			}
>> -		} else {
>> -			p->rSum += sql_value_double(argv[0]);
>> -			p->approx = 1;
>> +	assert(p != NULL);
> 
> Why are you sure, that p != NULL? sql_aggregate_context()
> on first invocation allocates memory, and it can fail.

Well, theoretically speaking - yes, it can. You’ve fixed that
in review fix, so skipped.

>> +	int type = sql_value_type(argv[0]);
>> +	if (type == SQL_NULL)
>> +		return;
> 
> I've fixed the comment above and also I see, that sumStep is
> rewritten almost completely, so it is time to convert it to
> Tarantool style. See the diff below and on the branch in a
> separate commit.

Thx. Diff is OK, applied.

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

* [tarantool-patches] Re: [PATCH 3/9] sql: use msgpack types instead of custom ones
  2019-04-22 18:02       ` Vladislav Shpilevoy
@ 2019-04-23 19:58         ` n.pettik
  0 siblings, 0 replies; 42+ messages in thread
From: n.pettik @ 2019-04-23 19:58 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

[-- Attachment #1: Type: text/plain, Size: 766 bytes --]


>> diff --git a/src/box/sql/date.c b/src/box/sql/date.c
>> index 5f5272ea3..1c7a5ad2d 100644
>> --- a/src/box/sql/date.c
>> +++ b/src/box/sql/date.c
>> @@ -932,12 +932,12 @@ isDate(sql_context * context, int argc, sql_value ** argv, DateTime * p)
>> {
>> 	int i, n;
>> 	const unsigned char *z;
>> -	int eType;
>> +	enum mp_type eType;
>> 	memset(p, 0, sizeof(*p));
>> 	if (argc == 0) {
>> 		return setDateTimeToCurrent(context, p);
>> 	}
>> -	if ((eType = sql_value_type(argv[0])) == SQL_FLOAT
>> +	if ((eType = sql_value_type(argv[0])) == MP_DOUBLE
>> 	    || eType == SQL_INTEGER) {
> 
> SQL_INTEGER is removed already. Please, replace with MP_INT.

Ok, removed. This was dead code, so compiler didn’t
show any warnings/compile errors.


[-- Attachment #2: Type: text/html, Size: 3154 bytes --]

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

* [tarantool-patches] Re: [PATCH 4/9] sql: introduce type boolean
  2019-04-22 18:02       ` Vladislav Shpilevoy
@ 2019-04-23 19:58         ` n.pettik
  2019-04-23 21:06           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 42+ messages in thread
From: n.pettik @ 2019-04-23 19:58 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
>> index 6b38e8e66..d70b64c45 100644
>> --- a/src/box/sql/expr.c
>> +++ b/src/box/sql/expr.c
>> @@ -3775,7 +3766,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
>>                }
>>        case TK_TRUE:
>>        case TK_FALSE: {
>> -                       vdbe_emit_bool(pParse->pVdbe, pExpr, target);
>> +                       sqlVdbeAddOp2(v, OP_Bool, op == TK_TRUE, target);
>>                        return target;
>>                }
>> 
>>> 6. I see, that all other 'case's set pExpr->type. Why don't you
>>> do it here?
>> 
>> It is required only for non-constant values. For literals it is assigned
>> in parse.y spanExpr().
> 
> 1. Then why case TK_INTEGER sets 'pExpr->type = FIELD_TYPE_INTEGER',
> TK_STRING sets 'pExpr->type = FIELD_TYPE_STRING;', TK_BLOB sets
> 'pExpr->type = FIELD_TYPE_SCALAR;' - I see all of them being set in
> parse.y.

Those are literals, so as I said above, their types are assigned
right during parsing. If you are asking “why do we need to do so?”,
then answer is “they are leaf nodes and it is most appropriate
moment to do so”.

> On the contrary, there are places which create a new Expr, and
> does not set type even in parse.y. For example:
> 
>    1240: A.pExpr = sql_expr_new_dequoted(pParse->db, TK_INTEGER, &sqlIntTokens[N]);

Speaking of this particular example, yes, it was buggy place.
I’ve fixed it in "sql: make predicates accept and return boolean" patch

> Here the expr is created and spanExpr() is not used. How does it
> work?
> 
>>> 8. Looking at all these type comparison prohibitions I am getting
>>> afraid of how will we select from SCALAR columns?
>>> 
>>> A schema:
>>> 
>>>   box.execute('CREATE TABLE t (id INTEGER PRIMARY KEY, a SCALAR UNIQUE);')
>>>   box.execute("INSERT INTO t VALUES (1, 1), (2, true), (3, false), (4, 'str')")
>>> 
>>> SQL select:
>>> 
>>>> box.execute('SELECT * FROM t WHERE a > 1')
>>>   ---
>>>   - error: 'Type mismatch: can not convert INTEGER to boolean'
>>>   ...
>>> 
>>> The same Lua select:
>>> 
>>>> box.space.T.index[1]:select({1}, {iterator = 'GT'})
>>>   ---
>>>   - - [4, 'str']
>>>   ...
>>> 
>>> In fact, now we can not use SCALAR in SQL for any comparisons
>>> because it will raise type mismatch on literally everything.
>>> 
>>> What are we going to do with it? IMO, we could add a flag like
>>> 'is_scalar' to struct Mem in its flags section, which would allow
>>> to compare this value with anything.
>> 
>> Konstantin suggested to extend struct Mem with field_type,
>> which is going to substitute this flag.
>> 
>>> Looks crutchy though. And is
>>> not a part of this issue of course, but should be filed into the
>>> issue list.
>>> 
>>> I see a similar issue https://github.com/tarantool/tarantool/issues/4124.
>>> Probably, it is worth extending it instead of opening new.
>> 
>> I consider this as a trade-off of using scalar: users shouldn’t use this
>> type until they are really have to. Nevertheless, they still can use CAST
>> operator and switch case, or write their own lua-function converting values
>> to required type.
> 
> 2. Users may have good designed schema and work in each request with only
> one type in such a column, but still they will get errors just because
> the iterator can not walk to the needed tree node. Lets modify my example:
> 
>    SELECT * FROM t WHERE a = 1;
> 
> This request implicitly says that it will work with numbers only, but
> somehow its success depends on the content of the space, not even
> related to '1' value. In a nutshell it means that insertion of something
> into a space can break existing requests not related to the inserted
> data. It is weird. Such scalar type would be useless.

Yep, we can observe the same behaviour in DB2. For instance:

db2 => create table t (id varchar(10))
DB20000I  The SQL command completed successfully.
db2 => insert into t values(1)
DB20000I  The SQL command completed successfully.
db2 => select id from t where id = 1

ID        
----------
1         

  1 record(s) selected.

db2 => insert into t values('abc’)
DB20000I  The SQL command completed successfully.
db2 => insert into t values(1)
DB20000I  The SQL command completed successfully.
db2 => select id from t where id = 1

ID        
----------
1         
SQL0420N  Invalid character found in a character string argument of the 
function "DECFLOAT".  SQLSTATE=22018

After insertion of unconvertible to integer value ‘abc’,
select starts to fail. Note that DB2 is considered to be
most compatible with ANSI according to members of
our server team.

>>>> + *
>>>> + * @param str String to be converted to boolean.
>>>> + *            Assumed to be null terminated.
>>>> + * @param result Resulting value of cast.
>>>> + * @retval 0 If string satisfies conditions above.
>>>> + * @retval -1 Otherwise.
>>>> + */
>>>> +static int
>>>> +str_cast_to_boolean(const char *str, bool *result)
>>>> +{
>>>> +	assert(str != NULL);
>>>> +	for (; *str == ' '; str++);
>>>> +	size_t rest_str_len = strlen(str);
>>>> +	if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
>>>> +		*result = true;
>>>> +		return 0;
>>>> +	}
>>>> +	if (rest_str_len == 5 && strncasecmp(str, "false", 5) == 0) {
>>>> +		*result = false;
>>>> +		return 0;
>>>> +	}
>>> 
>>> 16. I guess it is far from the most efficient implementation. You
>>> calculate length of the whole string before comparison, scanning
>>> its twice in the worst and most common case. Please, consider my
>>> fixes below. I did not test them though, so probably there are some
>>> typos.
>>> Note, that in my fixes I've already accounted my comment about
>>> trimming trailing whitespaces.
>>> 
>>> ============================================================================
>>> @@ -625,16 +625,20 @@ str_cast_to_boolean(const char *str, bool *result)
>>> {
>>> 	assert(str != NULL);
>>> 	for (; *str == ' '; str++);
>>> -	size_t rest_str_len = strlen(str);
>>> -	if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
>>> +	if (strncasecmp(str, "true", 4) == 0) {
>> 
>> We can’t use strncasecmp() without verification the fact that
>> rest_str_len (i.e. string length after skipping leading spaces) is >= 4.
> 
> 3.1. Why? According to the standard, strncasecmp() compares not more than
> 'n' characters taking into account terminating zero. I tried these
> examples to simulate our case (when 'str' points to the beginning of a
> possible 'true/false' token).
> 
> 	printf("%d\n", strncasecmp("", "true", 4));
> 	printf("%d\n", strncasecmp("t", "true", 4));
> 	printf("%d\n", strncasecmp("tr", "true", 4));
> 	printf("%d\n", strncasecmp("tru", "true", 4));
> 	printf("%d\n", strncasecmp("true   ", "true", 4));
> 
> I got this output:
> 
>    -116
>    -114
>    -117
>    -101
>    0
> 
> It means, that you do not need to call 'strlen' before
> 'strncasecmp'. I've dropped 'strlen' again and the tests
> passed. If you think that explicit length check is mandatory,
> then please, provide a test. Probably I'm wrong somewhere in
> my speculations.

Well, you are right: I missed the fact that it compares UP to n symbols.
I’m sorry to have confused you.

>> -       if (rest_str_len == 4 && strncasecmp(str, "true", 4) == 0) {
>> +       if (rest_str_len >= 4 && strncasecmp(str, "true", 4) == 0) {
>>                *result = true;
>> -               return 0;
>> -       }
>> -       if (rest_str_len == 5 && strncasecmp(str, "false", 5) == 0) {
>> +               str += 4;
>> +       } else if (rest_str_len >= 5 && strncasecmp(str, "false", 5) == 0) {
>>                *result = false;
>> -               return 0;
>> +               str += 5;
>> +       } else {
>> +               return -1;
>>        }
>> -       return -1;
>> +       for (; *str != '\0'; ++str) {
>> +               if (*str != ' ')
>> +                       return -1;
>> +       }
>> +       return 0;
>> }
> 
> My fixes, on the branch:

Ok, applied.

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

* [tarantool-patches] Re: [PATCH 5/9] sql: improve type determination for column meta
  2019-04-22 18:02       ` Vladislav Shpilevoy
@ 2019-04-23 19:58         ` n.pettik
  0 siblings, 0 replies; 42+ messages in thread
From: n.pettik @ 2019-04-23 19:58 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

[-- Attachment #1: Type: text/plain, Size: 2485 bytes --]


>> Secondly, suggested approach simply makes code cleaner and easier
>> to maintain: we shouldn’t care about adding on each branch of switch
>> appropriate type (since it is already done in sql_expr_type()).
>> 
>> Eventually, sqlExprCodeTarget() doesn’t take into consideration
>> types of child nodes. This drawback will appear when we extend
>> integer type with small/big ints:
>> 
>> Column a is of type smallint.
>> 
>> SELECT 1 + a;
>> 
>> Should result in ‘smallint' type, not common ‘number’.
>> Without tree traversal we can’t do that.
>> 
>> Price is a primitive traversal through the expr tree. This traversal (but in more
>> sophisticated implementation and being called only once saving in each node
>> its type) is anyway required to implement this task:
>> https://github.com/tarantool/tarantool/issues/3103 <https://github.com/tarantool/tarantool/issues/3103> <https://github.com/tarantool/tarantool/issues/3103 <https://github.com/tarantool/tarantool/issues/3103>>
> 
> I do not have anything against traversal. I just don't like multiple
> traversal and seemingly free and fast call of deceptive sql_expr_type() -
> it is expensive in fact, just look at this huge 'switch-case'.
> 
> Your patch is ok, it does not aggravate anything. But sql_expr_type()
> is called in very many places. Probably, in scope of 3103 we should
> set Expr type always before optimizer and Vdbe-coder are started. Just
> thoughts.
> 
>> In fact, we should once visit each node (probably after names resolution)
>> and save its type depending on child’s types.
>> 
>>> If you are not sure about the price, we could ask Alexander to run
>>> benches on your branch before pushing into the master.
>> 
>> This patch is auxiliary to main-patchset. I’ve added it to avoid
>> abusing changing result files like sql/types.result etc. Can drop it tho.
> 
> Now I see, that there is nothing to bench. Almost pure refactoring.
> 
>>>> So, to improve accuracy
>>>> of type determination, let's always call that method and remove type
>>>> assignment in sqlExprCodeTarget().
>>> 
>>> This patch fixes the issue
>>> https://github.com/tarantool/tarantool/issues/4126 <https://github.com/tarantool/tarantool/issues/4126>.
>> 
>> Ok, added label.
>> 
> 
> During the second review I found some orphan type assignments
> in codetarget(). I dropped them and the tests passed. The diff is
> on the branch.

Thx, applied as obvious.


[-- Attachment #2: Type: text/html, Size: 13743 bytes --]

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

* [tarantool-patches] Re: [PATCH 8/9] sql: make LIKE predicate return boolean result
  2019-04-22 18:02       ` Vladislav Shpilevoy
@ 2019-04-23 19:58         ` n.pettik
  0 siblings, 0 replies; 42+ messages in thread
From: n.pettik @ 2019-04-23 19:58 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


>> diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
>> index 1590a3550..69aea5ef5 100644
>> --- a/src/box/sql/vdbemem.c
>> +++ b/src/box/sql/vdbemem.c
>> @@ -803,7 +803,7 @@ sqlVdbeMemSetInt64(Mem * pMem, i64 val)
>> }
>> 
>> void
>> -vdbe_mem_set_boolean(struct Mem *mem, bool value)
>> +vdbe_mem_set_bool(struct Mem *mem, bool value)
>> {
>> 
> 
> 1. We now have mem_value_bool(), and so as to be consistent
> it would be better to name this function mem_set_bool(), not
> vdbe_mem_set_bool(). It does not take struct Vdbe pointer
> anyway.
> 
> 2. Is there any concrete reason why vdbe_mem_set_bool() is
> not used now in the following places?

No, there’s not.

> 
> vdbe.c:1101
> 	pOut = out2Prerelease(p, pOp);
> 	assert(pOp->p1 == 1 || pOp->p1 == 0);
> 	pOut->flags = MEM_Bool;
> 	pOut->u.b = pOp->p1;
> 	break;
> 
> vdbe.c:2275
> 	memAboutToChange(p, pOut);
> 	MemSetTypeFlag(pOut, MEM_Bool);
> 	pOut->u.b = res2;
> 	REGISTER_TRACE(pOp->p2, pOut);
> 
> vdbe.c:2471
> 	pOut->u.b = v1;
> 	MemSetTypeFlag(pOut, MEM_Bool);
> 
> vdbe.c:2495
> 	pOut->flags = MEM_Bool;
> 	pOut->u.b = ! pIn1->u.b;
> 
> If it works for them, then I propose to move vdbe_mem_set_bool()
> to the patch 'sql: introduce type boolean', and use it.

Ok, added changes to corresponding patches.

> Also, please, consider this minor review fix, on the branch:

Applied.

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

* [tarantool-patches] Re: [PATCH 9/9] sql: make <search condition> accept only boolean
  2019-04-22 18:02       ` Vladislav Shpilevoy
@ 2019-04-23 19:59         ` n.pettik
  2019-04-23 21:06           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 42+ messages in thread
From: n.pettik @ 2019-04-23 19:59 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


> On 18/04/2019 20:55, n.pettik wrote:
>> 
>>>> <search condition> is a predicate used as a part of WHERE and
>>>> JOIN clauses. ANSI SQL states that <search condition> must
>>>> accept only boolean arguments. In our SQL it is implemented as
>>>> bytecode instruction OP_If which in turn carries out logic of
>>>> conditional jump. Since it can be involved in executing other routines
>>>> different from <search condition>, 
>>> 
>>> 1. Which other routines? What is a valid case of OP_If with non-boolean
>>> value in check?
>> 
>> For instance, to verify that register containing LIMIT value is > 0.
> 
> Yes, and this is almost the only case. What is more, it happens only once
> per request, to check if LIMIT == 0 initially. Further it is decremented
> and checked via OP_IfNotZero and OP_DecrJumpZero.
> 
>> It is quite hard to track values which come to this opcode, so we
>> can’t be sure that it always accepts booleans.
> 
> It is hard, but without it
> 
> 1) You can't be sure, that really all the search conditions
>   are checked to be booleans;
> 
> 2) It makes OP_If/IfNot slower, and they are called repeatedly in
>   requests;

One branching worth nothing. Below you suggest to split opcode
into two (fix me if I’m wrong), which in turn affects performance way much more.

> 3) It adds one more flag SQL_BOOLREQ, which looks very crutchy.

IMHO it is matter of taste. Anyway, removed this flag.

> I tried myself to disable non-bool for OP_If/IfNot, and I fixed
> LIMIT cases. Then I've found CASE-WHEN also uses OP_If/IfNot in
> this distorted way.
> 
> tarantool> box.execute("SELECT CASE WHEN 0 THEN 'zero' WHEN 1 THEN 'one' END;”)

Fixed.

> It violates the standard. "Information technology —
> Database languages — SQL — Part 2: Foundation (SQL/Foundation)",
> 2011, page 230.
> 
> 'WHEN' is a search condition, but I've used '1', not 'true'.
> Also I tested it on PostgreSQL - they raise an error, so it is
> both standard and practically used way.
> 
> Below are my fixes for LIMIT and a small obvious refactoring,
> but they are *not on the branch* - not all the tests pass when I
> start banning non-bools in OP_If/IfNot.

I’ve fixed that.

> And I do not like these temporary registers.

They seem OK to me.

> Probably we could somehow reuse OP_IfNotZero,
> for example, via having it refactored to OP_CheckZero which would
> be able to jump both if the target is zero and if not. For example,
> it could look like this:
> 
>    /**
>     * P1 - a register with value to check if it equals zero.
>     * P3 - if 1, then check r[P1] == 0, if 0, then check r[P1] != 0.
>     * If the check is successful, then jump to P2.
>     * 
>     * if (r[P1] == 0) == P3 then jump p2
>     */
> 
> But I do not know what to do with its current version decrementing
> r[P1]. My ideas were about splitting 'decrement' operation from
> zero-checking. Then we could do these replacements:
> 
>    OP_IfNotZero -> OP_CheckZero, OP_Decrement
> 
>    OP_DecrJumpZero -> OP_Decrement, OP_CheckZero
> 
>    OP_IfNot -> OP_CheckZero (where it is used inappropriately)
> 
> And now we drop OP_IfNotZero and OP_DecrJumpZero, and forbid
> non-bool in OP_IfNot/OP_If. Perhaps we will find more standard
> violations.
> 
> ==================================================================

I’ve managed to make OP_If accept only booleans without splitting
opcodes. Diff (including your changes):

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 3c4cde2c8..86fc28606 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -1094,24 +1094,16 @@ sqlPExprAddSelect(Parse * pParse, Expr * pExpr, Select * pSelect)
  * LEFT JOIN, then we cannot determine at compile-time whether or not
  * is it true or false, so always return 0.
  */
-static int
+static inline bool
 exprAlwaysTrue(Expr * p)
 {
-       if (ExprHasProperty(p, EP_FromJoin))
-               return 0;
-       if (p->op == TK_TRUE)
-               return 1;
-       return 0;
+       return !ExprHasProperty(p, EP_FromJoin) && p->op == TK_TRUE;
 }
 
-static int
+static inline bool
 exprAlwaysFalse(Expr * p)
 {
-       if (ExprHasProperty(p, EP_FromJoin))
-               return 0;
-       if (p->op == TK_FALSE)
-               return 1;
-       return 0;
+       return !ExprHasProperty(p, EP_FromJoin) && p->op == TK_FALSE;
 }
 
 struct Expr *
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 428e3a92f..280391040 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -769,8 +769,10 @@ pushOntoSorter(Parse * pParse,             /* Parser context */
                pParse->nMem += pSort->nOBSat;
                nKey = nExpr - pSort->nOBSat + bSeq;
                if (bSeq) {
-                       addrFirst =
-                           sqlVdbeAddOp1(v, OP_IfNot, regBase + nExpr);
+                       int r1 = sqlGetTempReg(pParse);
+                       sqlVdbeAddOp2(v, OP_Integer, 0, r1);
+                       addrFirst = sqlVdbeAddOp3(v, OP_Eq, r1, 0, regBase + nExpr);
+                       sqlReleaseTempReg(pParse, r1);
                } else {
                        addrFirst =
                            sqlVdbeAddOp1(v, OP_SequenceTest,
@@ -799,9 +801,10 @@ pushOntoSorter(Parse * pParse,             /* Parser context */
                                  pSort->labelBkOut);
                sqlVdbeAddOp1(v, OP_ResetSorter, pSort->iECursor);
                if (iLimit) {
-                       sqlVdbeAddOp2(v, OP_IfNot, iLimit,
-                                         pSort->labelDone);
-                       VdbeCoverage(v);
+                       int r1 = sqlGetTempReg(pParse);
+                       sqlVdbeAddOp2(v, OP_Integer, 0, r1);
+                       sqlVdbeAddOp3(v, OP_Eq, r1, pSort->labelDone, iLimit);
+                       sqlReleaseTempReg(pParse, r1);
                }
                sqlVdbeJumpHere(v, addrFirst);
                sqlExprCodeMove(pParse, regBase, regPrevKey, pSort->nOBSat);
@@ -2115,11 +2118,10 @@ computeLimitRegisters(Parse * pParse, Select * p, int iBreak)
                                  P4_STATIC);
 
                sqlVdbeResolveLabel(v, positive_limit_label);
-               sqlReleaseTempReg(pParse, r1);
                VdbeCoverage(v);
                VdbeComment((v, "LIMIT counter"));
-               sqlVdbeAddOp2(v, OP_IfNot, iLimit, iBreak);
-               VdbeCoverage(v);
+               sqlVdbeAddOp3(v, OP_Eq, r1, iBreak, iLimit);
+               sqlReleaseTempReg(pParse, r1);
 
                if ((p->selFlags & SF_SingleRow) != 0) {
                        if (ExprHasProperty(p->pLimit, EP_System)) {
@@ -2673,10 +2675,11 @@ multiSelect(Parse * pParse,     /* Parsing context */
                                p->iLimit = pPrior->iLimit;
                                p->iOffset = pPrior->iOffset;
                                if (p->iLimit) {
-                                       addr =
-                                           sqlVdbeAddOp1(v, OP_IfNot,
-                                                             p->iLimit);
-                                       VdbeCoverage(v);
+                                       int r1 = sqlGetTempReg(pParse);
+                                       sqlVdbeAddOp2(v, OP_Integer, 0, r1);
+                                       addr = sqlVdbeAddOp3(v, OP_Eq, r1, 0,
+                                                            p->iLimit);
+                                       sqlReleaseTempReg(pParse, r1);
                                        VdbeComment((v,
                                                     "Jump ahead if LIMIT reached"));
                                        if (p->iOffset) {
@@ -3047,7 +3050,7 @@ generateOutputSubroutine(struct Parse *parse, struct Select *p,
                sqlVdbeJumpHere(v, addr1);
                sqlVdbeAddOp3(v, OP_Copy, in->iSdst, reg_prev + 1,
                                  in->nSdst - 1);
-               sqlVdbeAddOp2(v, OP_Integer, 1, reg_prev);
+               sqlVdbeAddOp2(v, OP_Bool, true, reg_prev);
        }
        if (parse->db->mallocFailed)
                return 0;
@@ -3370,7 +3373,7 @@ multiSelectOrderBy(Parse * pParse,        /* Parsing context */
                assert(nOrderBy >= expr_count || db->mallocFailed);
                regPrev = pParse->nMem + 1;
                pParse->nMem += expr_count + 1;
-               sqlVdbeAddOp2(v, OP_Integer, 0, regPrev);
+               sqlVdbeAddOp2(v, OP_Bool, 0, regPrev);
                key_info_dup = sql_key_info_new(db, expr_count);
                if (key_info_dup != NULL) {
                        for (int i = 0; i < expr_count; i++) {
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 51d30ee1b..b22598b67 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1721,8 +1721,8 @@ integer_overflow:
  * functions.
  *
  * If P1 is not zero, then it is a register that a subsequent min() or
- * max() aggregate will set to 1 if the current row is not the minimum or
- * maximum.  The P1 register is initialized to 0 by this instruction.
+ * max() aggregate will set to true if the current row is not the minimum or
+ * maximum.  The P1 register is initialized to false by this instruction.
  *
  * The interface used by the implementation of the aforementioned functions
  * to retrieve the collation sequence set by this opcode is not available
@@ -1731,7 +1731,7 @@ integer_overflow:
 case OP_CollSeq: {
        assert(pOp->p4type==P4_COLLSEQ || pOp->p4.pColl == NULL);
        if (pOp->p1) {
-               sqlVdbeMemSetInt64(&aMem[pOp->p1], 0);
+               mem_set_bool(&aMem[pOp->p1], false);
        }
        break;
 }
@@ -2563,21 +2563,10 @@ case OP_IfNot: {            /* jump, in1 */
        } else if ((pIn1->flags & MEM_Bool) != 0) {
                c = pOp->opcode==OP_IfNot ? ! pIn1->u.b : pIn1->u.b;
        } else {
-               if (pOp->p5 == OPFLAG_BOOLREQ) {
-                       diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-                                sql_value_text(pIn1), "boolean");
-                       rc = SQL_TARANTOOL_ERROR;
-                       goto abort_due_to_error;
-               }
-               double v;
-               if (sqlVdbeRealValue(pIn1, &v) != 0) {
-                       diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-                                sql_value_text(pIn1), "real");
-                       rc = SQL_TARANTOOL_ERROR;
-                       goto abort_due_to_error;
-               }
-               c = v != 0;
-               if (pOp->opcode==OP_IfNot) c = !c;
+               diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+                        sql_value_text(pIn1), "boolean");
+               rc = SQL_TARANTOOL_ERROR;
+               goto abort_due_to_error;
        }
        VdbeBranchTaken(c!=0, 2);
        if (c) {
@@ -5301,7 +5290,7 @@ case OP_AggStep: {
        if (pCtx->skipFlag) {
                assert(pOp[-1].opcode==OP_CollSeq);
                i = pOp[-1].p1;
-               if (i) sqlVdbeMemSetInt64(&aMem[i], 1);
+               if (i) mem_set_bool(&aMem[i], true);
        }
        break;
 }
diff --git a/test/sql-tap/cse.test.lua b/test/sql-tap/cse.test.lua
index 39c1cc4ca..78d0d2046 100755
--- a/test/sql-tap/cse.test.lua
+++ b/test/sql-tap/cse.test.lua
@@ -102,7 +102,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "cse-1.6.3",
     [[
-        SELECT CASE WHEN b THEN d WHEN e THEN f ELSE 999 END, b, c, d FROM t1
+        SELECT CASE WHEN CAST(b AS BOOLEAN) THEN d WHEN CAST(e AS BOOLEAN)  THEN f ELSE 999 END, b, c, d FROM t1
     ]], {
         -- <cse-1.6.3>
         13, 11, 12, 13, 23, 21, 22, 23
@@ -112,7 +112,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "cse-1.6.4",
     [[
-        SELECT b, c, d, CASE WHEN b THEN d WHEN e THEN f ELSE 999 END FROM t1
+        SELECT b, c, d, CASE WHEN CAST(b AS BOOLEAN) THEN d WHEN CAST(e AS BOOLEAN) THEN f ELSE 999 END FROM t1
     ]], {
         -- <cse-1.6.4>
         11, 12, 13, 13, 21, 22, 23, 23
@@ -122,7 +122,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "cse-1.6.5",
     [[
-        SELECT b, c, d, CASE WHEN 0 THEN d WHEN e THEN f ELSE 999 END FROM t1
+        SELECT b, c, d, CASE WHEN false THEN d WHEN CAST(e AS BOOLEAN) THEN f ELSE 999 END FROM t1
     ]], {
         -- <cse-1.6.5>
         11, 12, 13, 15, 21, 22, 23, 25
diff --git a/test/sql-tap/e_select1.test.lua b/test/sql-tap/e_select1.test.lua
index 9e9c4c551..c4724e636 100755
--- a/test/sql-tap/e_select1.test.lua
+++ b/test/sql-tap/e_select1.test.lua
@@ -703,7 +703,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "e_select-3.2.1b",
     [[
-        SELECT k FROM x1 LEFT JOIN x2 USING(k) WHERE x2.k
+        SELECT k FROM x1 LEFT JOIN x2 USING(k) WHERE x2.k <> 0
     ]], {
         -- <e_select-3.2.1b>
         1, 3, 5
diff --git a/test/sql-tap/orderby1.test.lua b/test/sql-tap/orderby1.test.lua
index ea03c494b..51e8d301f 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)
+          ) GROUP BY 1 HAVING length(b) <> 0
         )
         FROM abc;
     ]], {
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index 1bad7679f..6beeb34cb 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -1492,13 +1492,13 @@ test:do_catchsql_test(
         -- </select1-7.9>
     })
 
-test:do_execsql_test(
+test:do_catchsql_test(
     "select1-8.1",
     [[
         SELECT f1 FROM test1 WHERE 4.3+2.4 OR 1 ORDER BY f1
     ]], {
         -- <select1-8.1>
-        11, 33
+        1, 'Type mismatch: can not convert 6.7 to boolean'
         -- </select1-8.1>
     })
 
diff --git a/test/sql-tap/select2.test.lua b/test/sql-tap/select2.test.lua
index 7f0188d6f..e08c8b3b6 100755
--- a/test/sql-tap/select2.test.lua
+++ b/test/sql-tap/select2.test.lua
@@ -222,7 +222,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select2-4.3",
     [[
-        SELECT * FROM aa CROSS JOIN bb WHERE NOT b;
+        SELECT * FROM aa CROSS JOIN bb WHERE NOT b <> 0;
     ]], {
         -- <select2-4.3>
         1, 0, 3, 0
@@ -242,7 +242,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select2-4.5",
     [[
-        SELECT * FROM aa, bb WHERE NOT min(a,b);
+        SELECT * FROM aa, bb WHERE NOT min(a,b) <> 0;
     ]], {

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

* [tarantool-patches] Re: [PATCH 4/9] sql: introduce type boolean
  2019-04-23 19:58         ` n.pettik
@ 2019-04-23 21:06           ` Vladislav Shpilevoy
  0 siblings, 0 replies; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-23 21:06 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Hi! Thanks for the fixes!

>>> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
>>> index 6b38e8e66..d70b64c45 100644
>>> --- a/src/box/sql/expr.c
>>> +++ b/src/box/sql/expr.c
>>> @@ -3775,7 +3766,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
>>>                }
>>>        case TK_TRUE:
>>>        case TK_FALSE: {
>>> -                       vdbe_emit_bool(pParse->pVdbe, pExpr, target);
>>> +                       sqlVdbeAddOp2(v, OP_Bool, op == TK_TRUE, target);
>>>                        return target;
>>>                }
>>>
>>>> 6. I see, that all other 'case's set pExpr->type. Why don't you
>>>> do it here?
>>>
>>> It is required only for non-constant values. For literals it is assigned
>>> in parse.y spanExpr().
>>
>> 1. Then why case TK_INTEGER sets 'pExpr->type = FIELD_TYPE_INTEGER',
>> TK_STRING sets 'pExpr->type = FIELD_TYPE_STRING;', TK_BLOB sets
>> 'pExpr->type = FIELD_TYPE_SCALAR;' - I see all of them being set in
>> parse.y.
> 
> Those are literals, so as I said above, their types are assigned
> right during parsing. If you are asking “why do we need to do so?”,
> then answer is “they are leaf nodes and it is most appropriate
> moment to do so”.

No, my question was not about why they are set in parse.y, but why
are they set in sqlExprCodeTarget() again, after parse.y. But I've just
noticed, that the next patch "sql: improve type determination for column meta"
fixes that.

>>>> 8. Looking at all these type comparison prohibitions I am getting
>>>> afraid of how will we select from SCALAR columns?
>>>>
>>>> A schema:
>>>>
>>>>   box.execute('CREATE TABLE t (id INTEGER PRIMARY KEY, a SCALAR UNIQUE);')
>>>>   box.execute("INSERT INTO t VALUES (1, 1), (2, true), (3, false), (4, 'str')")
>>>>
>>>> SQL select:
>>>>
>>>>> box.execute('SELECT * FROM t WHERE a > 1')
>>>>   ---
>>>>   - error: 'Type mismatch: can not convert INTEGER to boolean'
>>>>   ...
>>>>
>>>> The same Lua select:
>>>>
>>>>> box.space.T.index[1]:select({1}, {iterator = 'GT'})
>>>>   ---
>>>>   - - [4, 'str']
>>>>   ...
>>>>
>>>> In fact, now we can not use SCALAR in SQL for any comparisons
>>>> because it will raise type mismatch on literally everything.
>>>>
>>>> What are we going to do with it? IMO, we could add a flag like
>>>> 'is_scalar' to struct Mem in its flags section, which would allow
>>>> to compare this value with anything.
>>>
>>> Konstantin suggested to extend struct Mem with field_type,
>>> which is going to substitute this flag.
>>>
>>>> Looks crutchy though. And is
>>>> not a part of this issue of course, but should be filed into the
>>>> issue list.
>>>>
>>>> I see a similar issue https://github.com/tarantool/tarantool/issues/4124.
>>>> Probably, it is worth extending it instead of opening new.
>>>
>>> I consider this as a trade-off of using scalar: users shouldn’t use this
>>> type until they are really have to. Nevertheless, they still can use CAST
>>> operator and switch case, or write their own lua-function converting values
>>> to required type.
>>
>> 2. Users may have good designed schema and work in each request with only
>> one type in such a column, but still they will get errors just because
>> the iterator can not walk to the needed tree node. Lets modify my example:
>>
>>    SELECT * FROM t WHERE a = 1;
>>
>> This request implicitly says that it will work with numbers only, but
>> somehow its success depends on the content of the space, not even
>> related to '1' value. In a nutshell it means that insertion of something
>> into a space can break existing requests not related to the inserted
>> data. It is weird. Such scalar type would be useless.
> 
> Yep, we can observe the same behaviour in DB2. For instance:

DB2 is a perfect example when we deal with ANSI, but SCALAR is not ANSI
and is not the same as VARCHAR. Its single and main purpose to be
comparable with anything. If it can't be compared with anything, then it
is not SCALAR.

BTW, this VARCHAR case is also weird, even being the standard. What if
I used "1" instead of simple 1?


Please, consider these additions, on the branch:

======================================================================
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index ef1a6a6bb..e0930f05d 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2568,7 +2568,7 @@ case OP_IfNot: {            /* jump, in1 */
 	if (pIn1->flags & MEM_Null) {
 		c = pOp->p3;
 	} else if ((pIn1->flags & MEM_Bool) != 0) {
-		c = pOp->opcode==OP_IfNot ? ! pIn1->u.b : pIn1->u.b;
+		c = pOp->opcode == OP_IfNot ? ! pIn1->u.b : pIn1->u.b;
 	} else {
 		double v;
 		if (sqlVdbeRealValue(pIn1, &v) != 0) {
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 713a5c686..8c1aa4a8a 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -667,16 +667,14 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type)
 		return 0;
 	case FIELD_TYPE_BOOLEAN:
 		if ((pMem->flags & MEM_Int) != 0) {
-			pMem->u.b = pMem->u.i;
-			MemSetTypeFlag(pMem, MEM_Bool);
+			mem_set_bool(pMem, pMem->u.i);
 			return 0;
 		}
 		if ((pMem->flags & MEM_Str) != 0) {
 			bool value;
 			if (str_cast_to_boolean(pMem->z, &value) != 0)
 				return -1;
-			MemSetTypeFlag(pMem, MEM_Bool);
-			pMem->u.b = value;
+			mem_set_bool(pMem, value);
 			return 0;
 		}
 		if ((pMem->flags & MEM_Bool) != 0)

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

* [tarantool-patches] Re: [PATCH 9/9] sql: make <search condition> accept only boolean
  2019-04-23 19:59         ` n.pettik
@ 2019-04-23 21:06           ` Vladislav Shpilevoy
  2019-04-23 22:01             ` n.pettik
  0 siblings, 1 reply; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-23 21:06 UTC (permalink / raw)
  To: tarantool-patches, n.pettik

Thanks for the fixes!

On 23/04/2019 22:59, n.pettik wrote:
> 
>> On 18/04/2019 20:55, n.pettik wrote:
>>>
>>>>> <search condition> is a predicate used as a part of WHERE and
>>>>> JOIN clauses. ANSI SQL states that <search condition> must
>>>>> accept only boolean arguments. In our SQL it is implemented as
>>>>> bytecode instruction OP_If which in turn carries out logic of
>>>>> conditional jump. Since it can be involved in executing other routines
>>>>> different from <search condition>, 
>>>>
>>>> 1. Which other routines? What is a valid case of OP_If with non-boolean
>>>> value in check?
>>>
>>> For instance, to verify that register containing LIMIT value is > 0.
>>
>> Yes, and this is almost the only case. What is more, it happens only once
>> per request, to check if LIMIT == 0 initially. Further it is decremented
>> and checked via OP_IfNotZero and OP_DecrJumpZero.
>>
>>> It is quite hard to track values which come to this opcode, so we
>>> can’t be sure that it always accepts booleans.
>>
>> It is hard, but without it
>>
>> 1) You can't be sure, that really all the search conditions
>>   are checked to be booleans;
>>
>> 2) It makes OP_If/IfNot slower, and they are called repeatedly in
>>   requests;
> 
> One branching worth nothing. Below you suggest to split opcode
> into two (fix me if I’m wrong), which in turn affects performance way much more.

The places which I proposed to split are called only once per request.
For example, OP_IfNot with iLimit was used for initial check that it is
not zero. All the next work with iLimit was being done via special
opcodes OP_DecrJumpZero and OP_IfNotZero.

On the other hand, if we branch inside OP_IfNot, we branch repeatedly,
in cycles, many times per request.

> 
>> 3) It adds one more flag SQL_BOOLREQ, which looks very crutchy.
> 
> IMHO it is matter of taste. Anyway, removed this flag.

You are right, it would have been, if OP_IfNot/OP_If had already
had some other flags. But you proposed to change the opcodes
dramatically, to add a new argument just for some minor cases.
And as a result it hid a bug with CASE-WHEN search condition.

>> It violates the standard. "Information technology —
>> Database languages — SQL — Part 2: Foundation (SQL/Foundation)",
>> 2011, page 230.
>>
>> 'WHEN' is a search condition, but I've used '1', not 'true'.
>> Also I tested it on PostgreSQL - they raise an error, so it is
>> both standard and practically used way.
>>
>> Below are my fixes for LIMIT and a small obvious refactoring,
>> but they are *not on the branch* - not all the tests pass when I
>> start banning non-bools in OP_If/IfNot.
> 
> I’ve fixed that.

But you still set OPFLAG_BOOLREQ and SQL_BOOLREQ. Why?
Also the commit message still describes this flag as a
key change of the patch.

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

* [tarantool-patches] Re: [PATCH 9/9] sql: make <search condition> accept only boolean
  2019-04-23 21:06           ` Vladislav Shpilevoy
@ 2019-04-23 22:01             ` n.pettik
  0 siblings, 0 replies; 42+ messages in thread
From: n.pettik @ 2019-04-23 22:01 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


> On 24 Apr 2019, at 00:06, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> On 23/04/2019 22:59, n.pettik wrote:
>> 
>>> On 18/04/2019 20:55, n.pettik wrote:
>>>> 
>>>>>> <search condition> is a predicate used as a part of WHERE and
>>>>>> JOIN clauses. ANSI SQL states that <search condition> must
>>>>>> accept only boolean arguments. In our SQL it is implemented as
>>>>>> bytecode instruction OP_If which in turn carries out logic of
>>>>>> conditional jump. Since it can be involved in executing other routines
>>>>>> different from <search condition>, 
>>>>> 
>>>>> 1. Which other routines? What is a valid case of OP_If with non-boolean
>>>>> value in check?
>>>> 
>>>> For instance, to verify that register containing LIMIT value is > 0.
>>> 
>>> Yes, and this is almost the only case. What is more, it happens only once
>>> per request, to check if LIMIT == 0 initially. Further it is decremented
>>> and checked via OP_IfNotZero and OP_DecrJumpZero.
>>> 
>>>> It is quite hard to track values which come to this opcode, so we
>>>> can’t be sure that it always accepts booleans.
>>> 
>>> It is hard, but without it
>>> 
>>> 1) You can't be sure, that really all the search conditions
>>>  are checked to be booleans;
>>> 
>>> 2) It makes OP_If/IfNot slower, and they are called repeatedly in
>>>  requests;
>> 
>> One branching worth nothing. Below you suggest to split opcode
>> into two (fix me if I’m wrong), which in turn affects performance way much more.
> 
> The places which I proposed to split are called only once per request.
> For example, OP_IfNot with iLimit was used for initial check that it is
> not zero. All the next work with iLimit was being done via special
> opcodes OP_DecrJumpZero and OP_IfNotZero.
> 
> On the other hand, if we branch inside OP_IfNot, we branch repeatedly,
> in cycles, many times per request.
>> 
>>> 3) It adds one more flag SQL_BOOLREQ, which looks very crutchy.
>> 
>> IMHO it is matter of taste. Anyway, removed this flag.
> 
> You are right, it would have been, if OP_IfNot/OP_If had already
> had some other flags. But you proposed to change the opcodes
> dramatically, to add a new argument just for some minor cases.
> And as a result it hid a bug with CASE-WHEN search condition.
> 
>>> It violates the standard. "Information technology —
>>> Database languages — SQL — Part 2: Foundation (SQL/Foundation)",
>>> 2011, page 230.
>>> 
>>> 'WHEN' is a search condition, but I've used '1', not 'true'.
>>> Also I tested it on PostgreSQL - they raise an error, so it is
>>> both standard and practically used way.
>>> 
>>> Below are my fixes for LIMIT and a small obvious refactoring,
>>> but they are *not on the branch* - not all the tests pass when I
>>> start banning non-bools in OP_If/IfNot.
>> 
>> I’ve fixed that.
> 
> But you still set OPFLAG_BOOLREQ and SQL_BOOLREQ. Why?
> Also the commit message still describes this flag as a
> key change of the patch.

OMG, sorry, I forgot to apply stashed changes and merge them.
Haste makes waste… I’ve pushed these updates:
(commit message fixed as well)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 86fc28606..31b724e23 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -727,12 +727,10 @@ codeVectorCompare(Parse * pParse, /* Code generator context */
                }
                if (opx == TK_EQ) {
                        sqlVdbeAddOp2(v, OP_IfNot, dest, addrDone);
-                       sqlVdbeChangeP5(v, OPFLAG_BOOLREQ);
                        VdbeCoverage(v);
                        p5 |= SQL_KEEPNULL;
                } else if (opx == TK_NE) {
                        sqlVdbeAddOp2(v, OP_If, dest, addrDone);
-                       sqlVdbeChangeP5(v, OPFLAG_BOOLREQ);
                        VdbeCoverage(v);
                        p5 |= SQL_KEEPNULL;
                } else {
@@ -4718,7 +4716,7 @@ exprCodeBetween(Parse * pParse,   /* Parsing and code generating context */
  * continues straight thru if the expression is false.
  *
  * If the expression evaluates to NULL (neither true nor false), then
- * take the jump if the flag is SQL_JUMPIFNULL.
+ * take the jump if the jumpIfNull flag is SQL_JUMPIFNULL.
  *
  * This code depends on the fact that certain token values (ex: TK_EQ)
  * are the same as opcode values (ex: OP_Eq) that implement the corresponding
@@ -4727,14 +4725,14 @@ exprCodeBetween(Parse * pParse, /* Parsing and code generating context */
  * below verify that the numbers are aligned correctly.
  */
 void
-sqlExprIfTrue(Parse * pParse, Expr * pExpr, int dest, int flags)
+sqlExprIfTrue(Parse * pParse, Expr * pExpr, int dest, int jumpIfNull)
 {
        Vdbe *v = pParse->pVdbe;
        int op = 0;
        int regFree1 = 0;
        int regFree2 = 0;
        int r1, r2;
-       int jumpIfNull = flags & SQL_JUMPIFNULL;
+
        assert(jumpIfNull == SQL_JUMPIFNULL || jumpIfNull == 0);
        if (NEVER(v == 0))
                return;         /* Existence of VDBE checked by caller */
@@ -4870,22 +4868,18 @@ sqlExprIfTrue(Parse * pParse, Expr * pExpr, int dest, int flags)
  * continues straight thru if the expression is true.
  *
  * If the expression evaluates to NULL (neither true nor false) then
- * jump if flags contains SQL_JUMPIFNULL or fall through if it doesn't.
- *
- * IF flags contains SQL_BOOLREQ then OP_If(Not) is supplied with
- * flag OPFLAG_BOOLREQ which forces additional verification of
- * its arguments. It is required to make sure that searching
- * condition is boolean (to disallow queries like ... WHERE 1+1;).
+ * jump if jumpIfNull is SQL_JUMPIFNULL or fall through if jumpIfNull
+ * is 0.
  */
 void
-sqlExprIfFalse(Parse * pParse, Expr * pExpr, int dest, int flags)
+sqlExprIfFalse(Parse * pParse, Expr * pExpr, int dest, int jumpIfNull)
 {
        Vdbe *v = pParse->pVdbe;
        int op = 0;
        int regFree1 = 0;
        int regFree2 = 0;
        int r1, r2;
-       int jumpIfNull = flags & SQL_JUMPIFNULL;
+
        assert(jumpIfNull == SQL_JUMPIFNULL || jumpIfNull == 0);
        if (NEVER(v == 0))
                return;         /* Existence of VDBE checked by caller */
@@ -5050,12 +5044,6 @@ sqlExprIfFalse(Parse * pParse, Expr * pExpr, int dest, int flags)
                                                         &regFree1);
                                sqlVdbeAddOp3(v, OP_IfNot, r1, dest,
                                                  jumpIfNull != 0);
-                               /*
-                                * Make sure that search condition
-                                * under WHERE clause returns boolean.
-                                */
-                               if ((flags & SQL_BOOLREQ) != 0)
-                                       sqlVdbeChangeP5(v, OPFLAG_BOOLREQ);
                                VdbeCoverage(v);
                                testcase(regFree1 == 0);
                                testcase(jumpIfNull == 0);
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index b8a1d5f54..07c887bb4 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1785,7 +1785,6 @@ struct Savepoint {
 #define SQL_KEEPNULL     0x40  /* Used by vector == or <> */
 #define SQL_NULLEQ       0x80  /* NULL=NULL */
 #define SQL_NOTNULL      0x90  /* Assert that operands are never NULL */
-#define SQL_BOOLREQ      0x100 /* Argument passed to OP_If must be boolean */
 
 /**
  * Return logarithm of tuple count in space.
@@ -2766,7 +2765,6 @@ struct Parse {
 #define OPFLAG_SYSTEMSP      0x20      /* OP_Open**: set if space pointer
                                         * points to system space.
                                         */
-#define OPFLAG_BOOLREQ       0x1000    /* OP_IF(Not): operand must be boolean. */
 
 /**
  * Prepare vdbe P5 flags for OP_{IdxInsert, IdxReplace, Update}
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 23a1bda7d..1bac59587 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2537,21 +2537,14 @@ case OP_Once: {             /* jump */
        break;
 }
 
-/* Opcode: If P1 P2 P3 * P5
+/* Opcode: If P1 P2 P3 * *
  *
- * Jump to P2 if the value in register P1 is true.  The value
- * is considered true if it is numeric and non-zero.  If the value
+ * Jump to P2 if the value in register P1 is true. If the value
  * in P1 is NULL then take the jump if and only if P3 is non-zero.
- *
- * In case P5 contains BOOLREQ flag, then argument is supposed
- * to be BOOLEAN. Otherwise, an error is raised. Such check is
- * required to restrict <search condition> used in WHERE and
- * JOIN clauses allowing only boolean values.
  */
-/* Opcode: IfNot P1 P2 P3 * P5
+/* Opcode: IfNot P1 P2 P3 * *
  *
- * Jump to P2 if the value in register P1 is False.  The value
- * is considered false if it has a numeric value of zero.  If the value
+ * Jump to P2 if the value in register P1 is False. If the value
  * in P1 is NULL then take the jump if and only if P3 is non-zero.
  */
 case OP_If:                 /* jump, in1 */
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 93020b148..19ee2d03a 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -4349,8 +4349,7 @@ sqlWhereBegin(Parse * pParse,     /* The parser context */
                if (nTabList == 0
                    || sqlExprIsConstantNotJoin(sWLB.pWC->a[ii].pExpr)) {
                        sqlExprIfFalse(pParse, sWLB.pWC->a[ii].pExpr,
-                                      pWInfo->iBreak,
-                                      SQL_JUMPIFNULL | SQL_BOOLREQ);
+                                          pWInfo->iBreak, SQL_JUMPIFNULL);
                        sWLB.pWC->a[ii].wtFlags |= TERM_CODED;
                }
        }
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index 5ee2efce7..a453fe979 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -1613,8 +1613,7 @@ sqlWhereCodeOneLoopStart(WhereInfo * pWInfo,      /* Complete information about the W
                         */
                        continue;
                }
-               sqlExprIfFalse(pParse, pE, addrCont,
-                              SQL_JUMPIFNULL | SQL_BOOLREQ);
+               sqlExprIfFalse(pParse, pE, addrCont, SQL_JUMPIFNULL);
                if (skipLikeAddr)
                        sqlVdbeJumpHere(v, skipLikeAddr);
                pTerm->wtFlags |= TERM_CODED;

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

* [tarantool-patches] Re: [PATCH 0/9] Introduce type BOOLEAN in SQL
  2019-04-14 15:03 [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL Nikita Pettik
                   ` (8 preceding siblings ...)
       [not found] ` <b2a84f129c2343d3da3311469cbb7b20488a21c2.1555252410.git.korablev@tarantool.org>
@ 2019-04-24 10:28 ` Vladislav Shpilevoy
  2019-04-25  8:46 ` Kirill Yukhin
  10 siblings, 0 replies; 42+ messages in thread
From: Vladislav Shpilevoy @ 2019-04-24 10:28 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik, Kirill Yukhin

The patchset LGTM.

On 14/04/2019 18:03, Nikita Pettik wrote:
> Branch: https://github.com/tarantool/tarantool/tree/np/gh-3648-sql-introduce-bool
> Issue: https://github.com/tarantool/tarantool/issues/3648
> Issue: https://github.com/tarantool/tarantool/issues/3723
> 
> This patch-set makes type BOOLEAN available in SQL.
> It consists of two main parts: first one itroduces basic boolean
> capabilities like new literals, column type, insert/select etc;
> second one makes predicates and search condition used in WHERE
> and JOIN clauses accept and return boolean values.
> 
> For ANSI compliance see thread in @dev with subject "[dev] Boolean type in SQL"
> 
> Nikita Pettik (9):
>   sql: refactor mem_apply_numeric_type()
>   sql: disallow text values participate in sum() aggregate
>   sql: use msgpack types instead of custom ones
>   sql: introduce type boolean
>   sql: improve type determination for column meta
>   sql: make comparison predicate return boolean
>   sql: make predicates accept and return boolean
>   sql: make LIKE predicate return boolean result
>   sql: make <search condition> accept only boolean
> 
>  extra/mkkeywordhash.c                              |   5 +
>  src/box/bind.c                                     |  37 +-
>  src/box/execute.c                                  |  21 +-
>  src/box/lua/execute.c                              |   7 +-
>  src/box/lua/lua_sql.c                              |  15 +-
>  src/box/sql/build.c                                |  11 +-
>  src/box/sql/date.c                                 |   2 +-
>  src/box/sql/expr.c                                 | 107 ++--
>  src/box/sql/func.c                                 | 155 +++---
>  src/box/sql/legacy.c                               |   2 +-
>  src/box/sql/parse.y                                |  19 +-
>  src/box/sql/parse_def.c                            |   5 +
>  src/box/sql/select.c                               |   4 +-
>  src/box/sql/sqlInt.h                               |  31 +-
>  src/box/sql/vdbe.c                                 | 154 +++---
>  src/box/sql/vdbeInt.h                              |  24 +-
>  src/box/sql/vdbeapi.c                              |  88 ++-
>  src/box/sql/vdbeaux.c                              |  31 +-
>  src/box/sql/vdbemem.c                              |  81 ++-
>  src/box/sql/where.c                                |   3 +-
>  src/box/sql/wherecode.c                            |   3 +-
>  src/box/sql/whereexpr.c                            |   2 +-
>  test/sql-tap/aggnested.test.lua                    |  10 +-
>  test/sql-tap/cse.test.lua                          |   6 +-
>  test/sql-tap/e_delete.test.lua                     |   8 +-
>  test/sql-tap/e_select1.test.lua                    |  54 +-
>  test/sql-tap/func.test.lua                         |   2 +-
>  .../gh-3251-string-pattern-comparison.test.lua     |  76 +--
>  test/sql-tap/identifier_case.test.lua              |  10 +-
>  test/sql-tap/in1.test.lua                          |   8 +-
>  test/sql-tap/in3.test.lua                          |   2 +-
>  test/sql-tap/join5.test.lua                        |  20 +-
>  test/sql-tap/limit.test.lua                        |   2 +-
>  test/sql-tap/misc3.test.lua                        |   2 +-
>  test/sql-tap/resolver01.test.lua                   |   2 +-
>  test/sql-tap/select1.test.lua                      |   4 +-
>  test/sql-tap/select2.test.lua                      |   8 +-
>  test/sql-tap/select4.test.lua                      |   2 +-
>  test/sql-tap/select5.test.lua                      |   3 +-
>  test/sql-tap/select6.test.lua                      |   4 +-
>  test/sql-tap/select9.test.lua                      |   6 +-
>  test/sql-tap/subquery.test.lua                     |   4 +-
>  test/sql-tap/tkt2832.test.lua                      |   2 +-
>  test/sql-tap/tkt3346.test.lua                      |   2 +-
>  test/sql-tap/tkt3541.test.lua                      |   2 +-
>  test/sql-tap/whereG.test.lua                       |   6 +-
>  test/sql/check-clear-ephemeral.result              |   2 +-
>  test/sql/gh-2347-max-int-literals.result           |   2 +-
>  test/sql/gh-3199-no-mem-leaks.result               |  18 +-
>  test/sql/gh-3888-values-blob-assert.result         |   2 +-
>  test/sql/persistency.result                        |   8 +-
>  test/sql/transition.result                         |   4 +-
>  test/sql/types.result                              | 605 ++++++++++++++++++++-
>  test/sql/types.test.lua                            | 132 +++++
>  54 files changed, 1362 insertions(+), 463 deletions(-)
> 
> -- 
> 2.15.1
> 
> 

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

* [tarantool-patches] Re: [PATCH 0/9] Introduce type BOOLEAN in SQL
  2019-04-14 15:03 [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL Nikita Pettik
                   ` (9 preceding siblings ...)
  2019-04-24 10:28 ` [tarantool-patches] Re: [PATCH 0/9] Introduce type BOOLEAN in SQL Vladislav Shpilevoy
@ 2019-04-25  8:46 ` Kirill Yukhin
  10 siblings, 0 replies; 42+ messages in thread
From: Kirill Yukhin @ 2019-04-25  8:46 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

Hello,

On 14 Apr 18:03, Nikita Pettik wrote:
> Branch: https://github.com/tarantool/tarantool/tree/np/gh-3648-sql-introduce-bool
> Issue: https://github.com/tarantool/tarantool/issues/3648
> Issue: https://github.com/tarantool/tarantool/issues/3723
> 
> This patch-set makes type BOOLEAN available in SQL.
> It consists of two main parts: first one itroduces basic boolean
> capabilities like new literals, column type, insert/select etc;
> second one makes predicates and search condition used in WHERE
> and JOIN clauses accept and return boolean values.
> 
> For ANSI compliance see thread in @dev with subject "[dev] Boolean type in SQL"

I've checked the patchset into master branch.

--
Regards, Kirill Yukhin

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

end of thread, other threads:[~2019-04-25  8:46 UTC | newest]

Thread overview: 42+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-04-14 15:03 [tarantool-patches] [PATCH 0/9] Introduce type BOOLEAN in SQL Nikita Pettik
2019-04-14 15:03 ` [tarantool-patches] [PATCH 1/9] sql: refactor mem_apply_numeric_type() Nikita Pettik
2019-04-14 15:04 ` [tarantool-patches] [PATCH 2/9] sql: disallow text values participate in sum() aggregate Nikita Pettik
2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
2019-04-18 17:54     ` n.pettik
2019-04-22 18:02       ` Vladislav Shpilevoy
2019-04-23 19:58         ` n.pettik
2019-04-14 15:04 ` [tarantool-patches] [PATCH 3/9] sql: use msgpack types instead of custom ones Nikita Pettik
2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
2019-04-18 17:54     ` n.pettik
2019-04-22 18:02       ` Vladislav Shpilevoy
2019-04-23 19:58         ` n.pettik
2019-04-14 15:04 ` [tarantool-patches] [PATCH 4/9] sql: introduce type boolean Nikita Pettik
2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
2019-04-18 17:54     ` n.pettik
2019-04-22 18:02       ` Vladislav Shpilevoy
2019-04-23 19:58         ` n.pettik
2019-04-23 21:06           ` Vladislav Shpilevoy
2019-04-14 15:04 ` [tarantool-patches] [PATCH 5/9] sql: improve type determination for column meta Nikita Pettik
2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
2019-04-18 17:54     ` n.pettik
2019-04-22 18:02       ` Vladislav Shpilevoy
2019-04-23 19:58         ` n.pettik
2019-04-14 15:04 ` [tarantool-patches] [PATCH 6/9] sql: make comparison predicate return boolean Nikita Pettik
2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
2019-04-18 17:54     ` n.pettik
2019-04-14 15:04 ` [tarantool-patches] [PATCH 7/9] sql: make predicates accept and " Nikita Pettik
2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
2019-04-18 17:55     ` n.pettik
2019-04-14 15:04 ` [tarantool-patches] [PATCH 9/9] sql: make <search condition> accept only boolean Nikita Pettik
2019-04-16 14:12   ` [tarantool-patches] " Vladislav Shpilevoy
2019-04-18 17:55     ` n.pettik
2019-04-22 18:02       ` Vladislav Shpilevoy
2019-04-23 19:59         ` n.pettik
2019-04-23 21:06           ` Vladislav Shpilevoy
2019-04-23 22:01             ` n.pettik
     [not found] ` <b2a84f129c2343d3da3311469cbb7b20488a21c2.1555252410.git.korablev@tarantool.org>
2019-04-16 14:12   ` [tarantool-patches] Re: [PATCH 8/9] sql: make LIKE predicate return boolean result Vladislav Shpilevoy
2019-04-18 17:55     ` n.pettik
2019-04-22 18:02       ` Vladislav Shpilevoy
2019-04-23 19:58         ` n.pettik
2019-04-24 10:28 ` [tarantool-patches] Re: [PATCH 0/9] Introduce type BOOLEAN in SQL Vladislav Shpilevoy
2019-04-25  8:46 ` Kirill Yukhin

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