Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v5 41/52] sql: introduce mem_to_number()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
@ 2021-04-09 20:53 ` Mergen Imeev via Tarantool-patches
  2021-04-13 23:25   ` Mergen Imeev via Tarantool-patches
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 42/52] sql: introduce mem_to_str() and mem_to_str0() Mergen Imeev via Tarantool-patches
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 20:53 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

This patch introduces mem_to_number(). This function is used to convert
MEM to MEM contains number.

Part of #5818
---
 src/box/sql/func.c |  2 +-
 src/box/sql/mem.c  | 60 +++++++++++++---------------------------------
 src/box/sql/mem.h  | 32 ++++++-------------------
 src/box/sql/vdbe.c |  2 +-
 4 files changed, 25 insertions(+), 71 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 0b85bf365..0282aec74 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1641,7 +1641,7 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 	if (type == MP_NIL || p == NULL)
 		return;
 	if (type != MP_DOUBLE && type != MP_INT && type != MP_UINT) {
-		if (mem_apply_numeric_type(argv[0]) != 0) {
+		if (type != MP_STR || mem_to_number(argv[0]) != 0) {
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
 				 mem_str(argv[0]), "number");
 			context->is_aborted = true;
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 75d4c4d18..9a0234e60 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -807,6 +807,21 @@ mem_to_double(struct Mem *mem)
 	return -1;
 }
 
+int
+mem_to_number(struct Mem *mem)
+{
+	if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
+		return 0;
+	if ((mem->flags & MEM_Bool) != 0)
+		return bool_to_int(mem);
+	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
+		if (bytes_to_int(mem) == 0)
+			return 0;
+		return bytes_to_double(mem);
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -1879,49 +1894,6 @@ registerTrace(int iReg, Mem *p) {
 }
 #endif
 
-int
-mem_apply_numeric_type(struct Mem *record)
-{
-	if ((record->flags & MEM_Str) == 0)
-		return -1;
-	int64_t integer_value;
-	bool is_neg;
-	if (sql_atoi64(record->z, &integer_value, &is_neg, record->n) == 0) {
-		mem_set_int(record, integer_value, is_neg);
-		return 0;
-	}
-	double float_value;
-	if (sqlAtoF(record->z, &float_value, record->n) == 0)
-		return -1;
-	mem_set_double(record, float_value);
-	return 0;
-}
-
-int
-vdbe_mem_numerify(struct Mem *mem)
-{
-	if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real | MEM_Null)) != 0)
-		return 0;
-	if ((mem->flags & MEM_Bool) != 0) {
-		mem->u.u = (uint64_t)mem->u.b;
-		mem->flags = MEM_UInt;
-		mem->field_type = FIELD_TYPE_UNSIGNED;
-		return 0;
-	}
-	assert((mem->flags & (MEM_Blob | MEM_Str)) != 0);
-	bool is_neg;
-	int64_t i;
-	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) == 0) {
-		mem_set_int(mem, i, is_neg);
-	} else {
-		double d;
-		if (sqlAtoF(mem->z, &d, mem->n) == 0)
-			return -1;
-		mem_set_double(mem, d);
-	}
-	return 0;
-}
-
 /*
  * Cast the datatype of the value in pMem according to the type
  * @type.  Casting is different from applying type in that a cast
@@ -2002,7 +1974,7 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type)
 	case FIELD_TYPE_DOUBLE:
 		return mem_to_double(pMem);
 	case FIELD_TYPE_NUMBER:
-		return vdbe_mem_numerify(pMem);
+		return mem_to_number(pMem);
 	case FIELD_TYPE_VARBINARY:
 		if ((pMem->flags & MEM_Blob) != 0)
 			return 0;
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index bf8c0f3b5..90f46af80 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -498,6 +498,13 @@ mem_to_int_precise(struct Mem *mem);
 int
 mem_to_double(struct Mem *mem);
 
+/**
+ * Convert the given MEM to NUMBER. This function defines the rules that are
+ * used to convert values of all other types to NUMBER.
+ */
+int
+mem_to_number(struct Mem *mem);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -531,31 +538,6 @@ registerTrace(int iReg, Mem *p);
 #define memIsValid(M) !mem_is_invalid(M)
 #endif
 
-/**
- * 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 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.
- * @retval -1 Otherwise.
- */
-int
-mem_apply_numeric_type(struct Mem *record);
-
-/**
- * Convert @a mem to NUMBER type, so that after conversion it has
- * one of types MEM_Real, MEM_Int or MEM_UInt. If conversion is
- * not possible, function returns -1.
- *
- * Beware - this function changes value and type of @a mem
- * argument.
- */
-int
-vdbe_mem_numerify(struct Mem *mem);
-
 int sqlVdbeMemCast(struct Mem *, enum field_type type);
 int sqlVdbeMemStringify(struct Mem *);
 int sqlVdbeMemNulTerminate(struct Mem *);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 90a901555..7880af248 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2712,7 +2712,7 @@ case OP_SeekGT: {       /* jump, in3 */
 		if (mem_is_null(pIn3))
 			goto skip_truncate;
 		if (mem_is_str(pIn3))
-			mem_apply_numeric_type(pIn3);
+			mem_to_number(pIn3);
 		int64_t i;
 		if (mem_is_uint(pIn3)) {
 			i = pIn3->u.u;
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v5 42/52] sql: introduce mem_to_str() and mem_to_str0()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 41/52] sql: introduce mem_to_number() Mergen Imeev via Tarantool-patches
@ 2021-04-09 20:53 ` Mergen Imeev via Tarantool-patches
  2021-04-13 22:58   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 43/52] sql: introduce mem_cast_explicit() Mergen Imeev via Tarantool-patches
                   ` (9 subsequent siblings)
  11 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 20:53 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

Thank you for the review! My answers and new patch below.


On 30.03.2021 02:07, Vladislav Shpilevoy wrote:
> Thanks for the patch!
>
> See 3 comments below.
>
> On 23.03.2021 10:36, imeevma@tarantool.org wrote:
>> This patch introduces mem_convert_to_string(). This function is used to
>> convert MEM to MEM contains string value.
>>
>> Part of #5818
>> ---
>>  src/box/sql/mem.c | 197 ++++++++++++++++++++++++++++++----------------
>>  src/box/sql/mem.h |   7 +-
>>  2 files changed, 133 insertions(+), 71 deletions(-)
>>
>> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
>> index 8600b5c41..262f48aca 100644
>> --- a/src/box/sql/mem.c
>> +++ b/src/box/sql/mem.c
>> @@ -818,6 +818,131 @@ mem_convert_to_number(struct Mem *mem)
>
> <...>
>
>> +
>> +static inline int
>> +mem_convert_boolean_to_string(struct Mem *mem)
>> +{
>> +	const char *str = mem->u.b ? "TRUE" : "FALSE";
>> +	return mem_copy_string0(mem, str);
>
> 1. This can be a static string. Because the string
> constants are not going anywhere, can ref them as is.
>
I tried to do this, but it breaks something in group_concat() built-in function.
Decided to left it as it is now.

> <...>
>
>> +
>> +int
>> +mem_convert_to_string0(struct Mem *mem)
>> +{
>> +	if ((mem->flags & MEM_Str) != 0 && (mem->flags & MEM_Term) != 0)
>
> 2. Can be done in one check:
>
> 	(mem->flags & (MEM_Str | MEM_Term)) == (MEM_Str | MEM_Term)
>
Thanks, fixed.

>> +		return 0;
>> +	if ((mem->flags & MEM_Str) != 0)
>> +		return mem_convert_string_to_string0(mem);
>> +	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
>> +		return mem_convert_integer_to_string(mem);
>> +	if ((mem->flags & MEM_Real) != 0)
>> +		return mem_convert_double_to_string(mem);
>> +	if ((mem->flags & MEM_Bool) != 0)
>> +		return mem_convert_boolean_to_string(mem);
>> +	if ((mem->flags & MEM_Blob) != 0) {
>> +		if ((mem->flags & MEM_Subtype) == 0)
>> +			return mem_convert_binary_to_string0(mem);
>> +		if (mp_typeof(*mem->z) == MP_MAP)
>> +			return mem_convert_map_to_string(mem);
>> +		return mem_convert_array_to_string(mem);
>> +	}
>> +	return -1;
>> +}
>> +
>> +int
>> +mem_convert_to_string(struct Mem *mem)
>
> 3. Why would you need a not terminated string? The old function
> always terminated the strings AFAIS.
>
Actually, to work with MEM we do not need NULL-terminated strings. They are
needed for debugging, built-in functions and somewhere in printf.c. I think that
if we come to a way to return converted to string value using mem_get_str0()
without changing MEM, we may get rid of NULL-terminateds string in MEM.
Depending on '\0' too much may lead to some unnecessary problems. For example
see #5938.

For example, if we create analogue of mem_str() that returns string
representation of value of MEM allocated on region, then there will be no need
of NULL-terminated strings in MEMs.

NULL-termination was needed in SQLite since string should be NULL-terminated
when it is returned to user. We do not have such problem since MEM converted to
msgpack which do not need NULL-termination.

>> +{
>> +	if ((mem->flags & MEM_Str) != 0)
>> +		return 0;
>> +	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
>> +		return mem_convert_integer_to_string(mem);
>> +	if ((mem->flags & MEM_Real) != 0)
>> +		return mem_convert_double_to_string(mem);
>> +	if ((mem->flags & MEM_Bool) != 0)
>> +		return mem_convert_boolean_to_string(mem);
>> +	if ((mem->flags & MEM_Blob) != 0) {
>> +		if ((mem->flags & MEM_Subtype) == 0)
>> +			return mem_convert_binary_to_string(mem);
>> +		if (mp_typeof(*mem->z) == MP_MAP)
>> +			return mem_convert_map_to_string(mem);
>> +		return mem_convert_array_to_string(mem);
>> +	}
>> +	return -1;
>> +}
>> +
>>  int
>>  mem_copy(struct Mem *to, const struct Mem *from)
>>  {


New patch:

commit 39f3cc5d8873f763fb0d609f64e026120cbf6de4
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 11:55:48 2021 +0300

    sql: introduce mem_to_str() and mem_to_str0()
    
    This patch introduces mem_to_str() and mem_to_str0() functions. These
    functions are used to convert a MEM to a MEM that contains string value.
    These functions defines the rules that are used during convertion from
    values of all other types to STRING.
    
    Part of #5818

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 9a0234e60..be7b47e76 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -822,6 +822,130 @@ mem_to_number(struct Mem *mem)
 	return -1;
 }
 
+static inline int
+int_to_str0(struct Mem *mem)
+{
+	const char *str;
+	if ((mem->flags & MEM_UInt) != 0)
+		str = tt_sprintf("%llu", mem->u.u);
+	else
+		str = tt_sprintf("%lld", mem->u.i);
+	return mem_copy_str0(mem, str);
+}
+
+static inline int
+array_to_str0(struct Mem *mem)
+{
+	const char *str = mp_str(mem->z);
+	return mem_copy_str0(mem, str);
+}
+
+static inline int
+map_to_str0(struct Mem *mem)
+{
+	const char *str = mp_str(mem->z);
+	return mem_copy_str0(mem, str);
+}
+
+static inline int
+bool_to_str0(struct Mem *mem)
+{
+	const char *str = mem->u.b ? "TRUE" : "FALSE";
+	return mem_copy_str0(mem, str);
+}
+
+static inline int
+bin_to_str(struct Mem *mem)
+{
+	if (ExpandBlob(mem) != 0)
+		return -1;
+	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
+		      MEM_Str;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
+static inline int
+bin_to_str0(struct Mem *mem)
+{
+	if (ExpandBlob(mem) != 0)
+		return -1;
+	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
+		return -1;
+	mem->z[mem->n] = '\0';
+	mem->flags = MEM_Str | MEM_Term;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
+static inline int
+double_to_str0(struct Mem *mem)
+{
+	if (sqlVdbeMemGrow(mem, BUF_SIZE, 0) != 0)
+		return -1;
+	sql_snprintf(BUF_SIZE, mem->z, "%!.15g", mem->u.r);
+	mem->n = strlen(mem->z);
+	mem->flags = MEM_Str | MEM_Term;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
+static inline int
+str_to_str0(struct Mem *mem)
+{
+	assert((mem->flags | MEM_Str) != 0);
+	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
+		return -1;
+	mem->z[mem->n] = '\0';
+	mem->flags |= MEM_Term;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
+int
+mem_to_str0(struct Mem *mem)
+{
+	if ((mem->flags & (MEM_Str | MEM_Term)) == (MEM_Str | MEM_Term))
+		return 0;
+	if ((mem->flags & MEM_Str) != 0)
+		return str_to_str0(mem);
+	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		return int_to_str0(mem);
+	if ((mem->flags & MEM_Real) != 0)
+		return double_to_str0(mem);
+	if ((mem->flags & MEM_Bool) != 0)
+		return bool_to_str0(mem);
+	if ((mem->flags & MEM_Blob) != 0) {
+		if ((mem->flags & MEM_Subtype) == 0)
+			return bin_to_str0(mem);
+		if (mp_typeof(*mem->z) == MP_MAP)
+			return map_to_str0(mem);
+		return array_to_str0(mem);
+	}
+	return -1;
+}
+
+int
+mem_to_str(struct Mem *mem)
+{
+	if ((mem->flags & MEM_Str) != 0)
+		return 0;
+	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		return int_to_str0(mem);
+	if ((mem->flags & MEM_Real) != 0)
+		return double_to_str0(mem);
+	if ((mem->flags & MEM_Bool) != 0)
+		return bool_to_str0(mem);
+	if ((mem->flags & MEM_Blob) != 0) {
+		if ((mem->flags & MEM_Subtype) == 0)
+			return bin_to_str(mem);
+		if (mp_typeof(*mem->z) == MP_MAP)
+			return map_to_str0(mem);
+		return array_to_str0(mem);
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -1517,7 +1641,7 @@ valueToText(sql_value * pVal)
 		pVal->flags |= MEM_Str;
 		sqlVdbeMemNulTerminate(pVal);	/* IMP: R-31275-44060 */
 	} else {
-		sqlVdbeMemStringify(pVal);
+		mem_to_str(pVal);
 		assert(0 == (1 & SQL_PTR_TO_INT(pVal->z)));
 	}
 	return pVal->z;
@@ -2001,74 +2125,6 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type)
 	}
 }
 
-/*
- * Add MEM_Str to the set of representations for the given Mem.  Numbers
- * are converted using sql_snprintf().  Converting a BLOB to a string
- * is a no-op.
- *
- * Existing representations MEM_Int and MEM_Real are invalidated if
- * bForce is true but are retained if bForce is false.
- *
- * A MEM_Null value will never be passed to this function. This function is
- * used for converting values to text for returning to the user (i.e. via
- * sql_value_text()), or for ensuring that values to be used as btree
- * keys are strings. In the former case a NULL pointer is returned the
- * user and the latter is an internal programming error.
- */
-int
-sqlVdbeMemStringify(Mem * pMem)
-{
-	int fg = pMem->flags;
-	int nByte = 32;
-
-	if ((fg & (MEM_Null | MEM_Str | MEM_Blob)) != 0 &&
-	    !mem_has_msgpack_subtype(pMem))
-		return 0;
-
-	assert(!(fg & MEM_Zero));
-	assert((fg & (MEM_Int | MEM_UInt | MEM_Real | MEM_Bool |
-		      MEM_Blob)) != 0);
-	assert(EIGHT_BYTE_ALIGNMENT(pMem));
-
-	/*
-	 * In case we have ARRAY/MAP we should save decoded value
-	 * before clearing pMem->z.
-	 */
-	char *value = NULL;
-	if (mem_has_msgpack_subtype(pMem)) {
-		const char *value_str = mp_str(pMem->z);
-		nByte = strlen(value_str) + 1;
-		value = region_alloc(&fiber()->gc, nByte);
-		memcpy(value, value_str, nByte);
-	}
-
-	if (sqlVdbeMemClearAndResize(pMem, nByte)) {
-		return -1;
-	}
-	if (fg & MEM_Int) {
-		sql_snprintf(nByte, pMem->z, "%lld", pMem->u.i);
-		pMem->flags &= ~MEM_Int;
-	} else if ((fg & MEM_UInt) != 0) {
-		sql_snprintf(nByte, pMem->z, "%llu", pMem->u.u);
-		pMem->flags &= ~MEM_UInt;
-	} else if ((fg & MEM_Bool) != 0) {
-		sql_snprintf(nByte, pMem->z, "%s",
-			     SQL_TOKEN_BOOLEAN(pMem->u.b));
-		pMem->flags &= ~MEM_Bool;
-	} else if (mem_has_msgpack_subtype(pMem)) {
-		sql_snprintf(nByte, pMem->z, "%s", value);
-		pMem->flags &= ~MEM_Subtype;
-		pMem->subtype = SQL_SUBTYPE_NO;
-	} else {
-		assert(fg & MEM_Real);
-		sql_snprintf(nByte, pMem->z, "%!.15g", pMem->u.r);
-		pMem->flags &= ~MEM_Real;
-	}
-	pMem->n = sqlStrlen30(pMem->z);
-	pMem->flags |= MEM_Str | MEM_Term;
-	return 0;
-}
-
 /*
  * Make sure the given Mem is \u0000 terminated.
  */
@@ -2191,7 +2247,7 @@ mem_apply_type(struct Mem *record, enum field_type type)
 		 */
 		if ((record->flags & MEM_Str) == 0 &&
 		    (record->flags & (MEM_Real | MEM_Int | MEM_UInt)) != 0)
-			sqlVdbeMemStringify(record);
+			mem_to_str(record);
 		record->flags &= ~(MEM_Real | MEM_Int | MEM_UInt);
 		return 0;
 	case FIELD_TYPE_VARBINARY:
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 90f46af80..146d08a2a 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -505,6 +505,23 @@ mem_to_double(struct Mem *mem);
 int
 mem_to_number(struct Mem *mem);
 
+/**
+ * Convert the given MEM to STRING. This function and the function below define
+ * the rules that are used to convert values of all other types to STRING. In
+ * this function, the string received after the convertion may be not
+ * NULL-terminated.
+ */
+int
+mem_to_str(struct Mem *mem);
+
+/**
+ * Convert the given MEM to STRING. This function and the function above define
+ * the rules that are used to convert values of all other types to STRING. In
+ * this function, the string received after convertion is NULL-terminated.
+ */
+int
+mem_to_str0(struct Mem *mem);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -539,7 +556,6 @@ registerTrace(int iReg, Mem *p);
 #endif
 
 int sqlVdbeMemCast(struct Mem *, enum field_type type);
-int sqlVdbeMemStringify(struct Mem *);
 int sqlVdbeMemNulTerminate(struct Mem *);
 int sqlVdbeMemExpandBlob(struct Mem *);
 #define ExpandBlob(P) (mem_is_zerobin(P)? sqlVdbeMemExpandBlob(P) : 0)

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

* [Tarantool-patches] [PATCH v5 43/52] sql: introduce mem_cast_explicit()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 41/52] sql: introduce mem_to_number() Mergen Imeev via Tarantool-patches
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 42/52] sql: introduce mem_to_str() and mem_to_str0() Mergen Imeev via Tarantool-patches
@ 2021-04-09 20:53 ` Mergen Imeev via Tarantool-patches
  2021-04-13 22:59   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 44/52] sql: introduce mem_cast_implicit() Mergen Imeev via Tarantool-patches
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 20:53 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

Thank you for the review! My answer and new patch below.


On 30.03.2021 02:08, Vladislav Shpilevoy wrote:
> Thanks for the patch!
>
> In function names we use subject_action_object pattern. So it
> should be mem_cast_explicit(), the same for the implicit cast.
Fixed.


New patch:

commit 83460913f3fdab50eb49ee0b3d84a69da6e29128
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 12:27:09 2021 +0300

    sql: introduce mem_cast_explicit()
    
    This patch introduces mem_cast_explicit(). This function is used to
    convert a MEM to a given field type according to explicit cast rules.
    
    Part of #5818

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index be7b47e76..45d2d5fe3 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -946,6 +946,131 @@ mem_to_str(struct Mem *mem)
 	return -1;
 }
 
+static inline int
+bytes_to_uint(struct Mem *mem)
+{
+	bool is_neg;
+	int64_t i;
+	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
+		return -1;
+	if (is_neg)
+		return -1;
+	mem_set_uint(mem, (uint64_t)i);
+	return 0;
+}
+
+static inline int
+str_to_bool(struct Mem *mem)
+{
+	char *str = mem->z;
+	bool b;
+	const char *str_true = "TRUE";
+	const char *str_false = "FALSE";
+	uint32_t len_true = strlen(str_true);
+	uint32_t len_false = strlen(str_false);
+
+	for (; str[0] == ' '; str++);
+	if (strncasecmp(str, str_true, len_true) == 0) {
+		b = true;
+		str += len_true;
+	} else if (strncasecmp(str, str_false, len_false) == 0) {
+		b = false;
+		str += len_false;
+	} else {
+		return -1;
+	}
+	for (; str[0] == ' '; str++);
+	if (str[0] != '\0')
+		return -1;
+	mem_set_bool(mem, b);
+	return 0;
+}
+
+static inline int
+int_to_bool(struct Mem *mem)
+{
+	mem->u.b = mem->u.i != 0;
+	mem->flags = MEM_Bool;
+	mem->field_type = FIELD_TYPE_BOOLEAN;
+	return 0;
+}
+
+static inline int
+double_to_bool(struct Mem *mem)
+{
+	mem->u.b = mem->u.r != 0.;
+	mem->flags = MEM_Bool;
+	mem->field_type = FIELD_TYPE_BOOLEAN;
+	return 0;
+}
+
+static inline int
+str_to_bin(struct Mem *mem)
+{
+	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
+		     MEM_Blob;
+	mem->field_type = FIELD_TYPE_VARBINARY;
+	return 0;
+}
+
+int
+mem_cast_explicit(struct Mem *mem, enum field_type type)
+{
+	if ((mem->flags & MEM_Null) != 0) {
+		mem->field_type = type;
+		return 0;
+	}
+	switch (type) {
+	case FIELD_TYPE_UNSIGNED:
+		if ((mem->flags & MEM_UInt) != 0)
+			return 0;
+		if ((mem->flags & MEM_Int) != 0)
+			return -1;
+		if ((mem->flags & MEM_Blob) != 0 &&
+		    (mem->flags & MEM_Subtype) != 0)
+			return -1;
+		if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+			return bytes_to_uint(mem);
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_int(mem);
+		if ((mem->flags & MEM_Bool) != 0)
+			return bool_to_int(mem);
+		return -1;
+	case FIELD_TYPE_STRING:
+		return mem_to_str(mem);
+	case FIELD_TYPE_DOUBLE:
+		return mem_to_double(mem);
+	case FIELD_TYPE_INTEGER:
+		return mem_to_int(mem);
+	case FIELD_TYPE_BOOLEAN:
+		if ((mem->flags & MEM_Bool) != 0)
+			return 0;
+		if ((mem->flags & (MEM_UInt | MEM_Int)) != 0)
+			return int_to_bool(mem);
+		if ((mem->flags & MEM_Str) != 0)
+			return str_to_bool(mem);
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_bool(mem);
+		return -1;
+	case FIELD_TYPE_VARBINARY:
+		if ((mem->flags & MEM_Blob) != 0)
+			return 0;
+		if ((mem->flags & MEM_Str) != 0)
+			return str_to_bin(mem);
+		return -1;
+	case FIELD_TYPE_NUMBER:
+		return mem_to_number(mem);
+	case FIELD_TYPE_SCALAR:
+		if ((mem->flags & MEM_Blob) != 0 &&
+		    (mem->flags & MEM_Subtype) != 0)
+			return -1;
+		return 0;
+	default:
+		break;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -1647,42 +1772,6 @@ valueToText(sql_value * pVal)
 	return pVal->z;
 }
 
-/**
- * According to ANSI SQL string value can be converted to boolean
- * type if string consists of literal "true" or "false" and
- * number of leading and trailing 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[out] 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++);
-	if (strncasecmp(str, SQL_TOKEN_TRUE, strlen(SQL_TOKEN_TRUE)) == 0) {
-		*result = true;
-		str += 4;
-	} else if (strncasecmp(str, SQL_TOKEN_FALSE,
-			       strlen(SQL_TOKEN_FALSE)) == 0) {
-		*result = false;
-		str += 5;
-	} else {
-		return -1;
-	}
-	for (; *str != '\0'; ++str) {
-		if (*str != ' ')
-			return -1;
-	}
-	return 0;
-}
-
 /*
  * Convert a 64-bit IEEE double into a 64-bit signed integer.
  * If the double is out of range of a 64-bit signed integer then
@@ -2018,113 +2107,6 @@ registerTrace(int iReg, Mem *p) {
 }
 #endif
 
-/*
- * Cast the datatype of the value in pMem according to the type
- * @type.  Casting is different from applying type in that a cast
- * is forced.  In other words, the value is converted into the desired
- * type even if that results in loss of data.  This routine is
- * used (for example) to implement the SQL "cast()" operator.
- */
-int
-sqlVdbeMemCast(Mem * pMem, enum field_type type)
-{
-	assert(type < field_type_MAX);
-	if (pMem->flags & MEM_Null)
-		return 0;
-	switch (type) {
-	case FIELD_TYPE_SCALAR:
-		return 0;
-	case FIELD_TYPE_BOOLEAN:
-		if ((pMem->flags & MEM_Int) != 0) {
-			mem_set_bool(pMem, pMem->u.i);
-			return 0;
-		}
-		if ((pMem->flags & MEM_UInt) != 0) {
-			mem_set_bool(pMem, pMem->u.u);
-			return 0;
-		}
-		if ((pMem->flags & MEM_Real) != 0) {
-			mem_set_bool(pMem, pMem->u.r);
-			return 0;
-		}
-		if ((pMem->flags & MEM_Str) != 0) {
-			bool value;
-			if (str_cast_to_boolean(pMem->z, &value) != 0)
-				return -1;
-			mem_set_bool(pMem, value);
-			return 0;
-		}
-		if ((pMem->flags & MEM_Bool) != 0)
-			return 0;
-		return -1;
-	case FIELD_TYPE_INTEGER:
-	case FIELD_TYPE_UNSIGNED:
-		if ((pMem->flags & (MEM_Blob | MEM_Str)) != 0) {
-			bool is_neg;
-			int64_t val;
-			if (sql_atoi64(pMem->z, &val, &is_neg, pMem->n) != 0)
-				return -1;
-			if (type == FIELD_TYPE_UNSIGNED && is_neg)
-				return -1;
-			mem_set_int(pMem, val, is_neg);
-			return 0;
-		}
-		if ((pMem->flags & MEM_Bool) != 0) {
-			pMem->u.u = (uint64_t)pMem->u.b;
-			pMem->flags = MEM_UInt;
-			pMem->field_type = FIELD_TYPE_UNSIGNED;
-			return 0;
-		}
-		if ((pMem->flags & MEM_Real) != 0) {
-			double d = pMem->u.r;
-			if (d < 0. && d >= (double)INT64_MIN) {
-				pMem->u.i = (int64_t)d;
-				pMem->flags = MEM_Int;
-				pMem->field_type = FIELD_TYPE_INTEGER;
-				return 0;
-			}
-			if (d >= 0. && d < (double)UINT64_MAX) {
-				pMem->u.u = (uint64_t)d;
-				pMem->flags = MEM_UInt;
-				pMem->field_type = FIELD_TYPE_UNSIGNED;
-				return 0;
-			}
-			return -1;
-		}
-		if (type == FIELD_TYPE_UNSIGNED &&
-		    (pMem->flags & MEM_UInt) == 0)
-			return -1;
-		return 0;
-	case FIELD_TYPE_DOUBLE:
-		return mem_to_double(pMem);
-	case FIELD_TYPE_NUMBER:
-		return mem_to_number(pMem);
-	case FIELD_TYPE_VARBINARY:
-		if ((pMem->flags & MEM_Blob) != 0)
-			return 0;
-		if ((pMem->flags & MEM_Str) != 0) {
-			MemSetTypeFlag(pMem, MEM_Str);
-			return 0;
-		}
-		return -1;
-	default:
-		assert(type == FIELD_TYPE_STRING);
-		assert(MEM_Str == (MEM_Blob >> 3));
-		if ((pMem->flags & MEM_Bool) != 0) {
-			const char *str_bool = SQL_TOKEN_BOOLEAN(pMem->u.b);
-			if (mem_copy_str0(pMem, str_bool) != 0)
-				return -1;
-			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);
-		pMem->flags &=
-			~(MEM_Int | MEM_UInt | MEM_Real | MEM_Blob | MEM_Zero);
-		return 0;
-	}
-}
-
 /*
  * Make sure the given Mem is \u0000 terminated.
  */
@@ -2282,43 +2264,6 @@ mem_apply_type(struct Mem *record, enum field_type type)
 	}
 }
 
-/**
- * Convert the numeric value contained in MEM to unsigned.
- *
- * @param mem The MEM that contains the numeric value.
- * @retval 0 if the conversion was successful, -1 otherwise.
- */
-static int
-mem_convert_to_unsigned(struct Mem *mem)
-{
-	if ((mem->flags & MEM_UInt) != 0)
-		return 0;
-	if ((mem->flags & MEM_Int) != 0)
-		return -1;
-	if ((mem->flags & MEM_Real) == 0)
-		return -1;
-	double d = mem->u.r;
-	if (d < 0.0 || d >= (double)UINT64_MAX)
-		return -1;
-	mem->u.u = (uint64_t)d;
-	mem->flags = MEM_UInt;
-	mem->field_type = FIELD_TYPE_UNSIGNED;
-	return 0;
-}
-
-int
-mem_convert_to_numeric(struct Mem *mem, enum field_type type)
-{
-	assert(mem_is_num(mem) && sql_type_is_numeric(type));
-	assert(type != FIELD_TYPE_NUMBER);
-	if (type == FIELD_TYPE_DOUBLE)
-		return mem_to_double(mem);
-	if (type == FIELD_TYPE_UNSIGNED)
-		return mem_convert_to_unsigned(mem);
-	assert(type == FIELD_TYPE_INTEGER);
-	return mem_to_int(mem);
-}
-
 static int
 sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 146d08a2a..73d2ffd6a 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -522,6 +522,10 @@ mem_to_str(struct Mem *mem);
 int
 mem_to_str0(struct Mem *mem);
 
+/** Convert the given MEM to given type according to explicit cast rules. */
+int
+mem_cast_explicit(struct Mem *mem, enum field_type type);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -555,7 +559,6 @@ registerTrace(int iReg, Mem *p);
 #define memIsValid(M) !mem_is_invalid(M)
 #endif
 
-int sqlVdbeMemCast(struct Mem *, enum field_type type);
 int sqlVdbeMemNulTerminate(struct Mem *);
 int sqlVdbeMemExpandBlob(struct Mem *);
 #define ExpandBlob(P) (mem_is_zerobin(P)? sqlVdbeMemExpandBlob(P) : 0)
@@ -598,17 +601,6 @@ void sql_value_apply_type(struct Mem *val, enum field_type type);
 int
 mem_apply_type(struct Mem *record, enum field_type type);
 
-/**
- * Convert the numeric value contained in MEM to another numeric
- * type.
- *
- * @param mem The MEM that contains the numeric value.
- * @param type The type to convert to.
- * @retval 0 if the conversion was successful, -1 otherwise.
- */
-int
-mem_convert_to_numeric(struct Mem *mem, enum field_type type);
-
 /** Setters = Change MEM value. */
 
 int sqlVdbeMemClearAndResize(struct Mem * pMem, int n);
@@ -622,12 +614,6 @@ struct Mem *sqlValueNew(struct sql *);
 void
 releaseMemArray(Mem * p, int N);
 
-/*
- * Clear any existing type flags from a Mem and replace them with f
- */
-#define MemSetTypeFlag(p, f) \
-   ((p)->flags = ((p)->flags&~(MEM_TypeMask|MEM_Zero))|f)
-
 /** Getters. */
 
 int
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 7880af248..29634841a 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1480,7 +1480,7 @@ case OP_Cast: {                  /* in1 */
 	pIn1 = &aMem[pOp->p1];
 	if (ExpandBlob(pIn1) != 0)
 		goto abort_due_to_error;
-	rc = sqlVdbeMemCast(pIn1, pOp->p2);
+	rc = mem_cast_explicit(pIn1, pOp->p2);
 	/*
 	 * SCALAR is not type itself, but rather an aggregation
 	 * of types. Hence, cast to this type shouldn't change
@@ -2100,7 +2100,7 @@ case OP_ApplyType: {
 			if (!mem_is_num(pIn1))
 				goto type_mismatch;
 			/* Try to convert numeric-to-numeric. */
-			if (mem_convert_to_numeric(pIn1, type) != 0)
+			if (mem_cast_explicit(pIn1, type) != 0)
 				goto type_mismatch;
 		}
 		pIn1++;

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

* [Tarantool-patches] [PATCH v5 44/52] sql: introduce mem_cast_implicit()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
                   ` (2 preceding siblings ...)
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 43/52] sql: introduce mem_cast_explicit() Mergen Imeev via Tarantool-patches
@ 2021-04-09 20:53 ` Mergen Imeev via Tarantool-patches
  2021-04-13 22:59   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 45/52] sql: introduce mem_get_int() Mergen Imeev via Tarantool-patches
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 20:53 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

Thank you for the review! My answer and new patch below.


On 30.03.2021 02:08, Vladislav Shpilevoy wrote:
> Thanks for the patch!
>
>> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
>> index 559bf6121..1baf4c482 100644
>> --- a/src/box/sql/mem.c
>> +++ b/src/box/sql/mem.c
>> @@ -1071,6 +1093,140 @@ mem_explicit_cast(struct Mem *mem, enum field_type type)
>
> <...>
>
>> +
>> +int
>> +mem_implicit_cast_old(struct Mem *mem, enum field_type type)
>
> What is this?
>
Last year there was a patch-set that should have removed implicit cast from SQL.
However, only part of this patch-set was pushed to master, because second review
wasn't completed. This lead to a situation where old and new implicit cast rules
exist at the same time and applied to different parts SQL. Also, @tsafin wants
to return old implicit cast rules. In short, the situation with implicit cast is
a mess right now. Still, I hope that we will remove implicit cast as we wanted
last year.

>> +{
>> +	if (mem_is_null(mem))
>> +		return 0;
>> +	switch (type) {
>> +	case FIELD_TYPE_UNSIGNED:
>> +		if ((mem->flags & MEM_UInt) != 0)
>> +			return 0;
>> +		if ((mem->flags & MEM_Real) != 0)
>> +			return mem_convert_double_to_unsigned_lossless(mem);
>> +		if ((mem->flags & MEM_Str) != 0)
>> +			return mem_convert_varstring_to_unsigned(mem);
>> +		return -1;
>> +	case FIELD_TYPE_STRING:
>> +		if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
>> +			return 0;
>> +		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
>> +			return mem_convert_integer_to_string(mem);
>> +		if ((mem->flags & MEM_Real) != 0)
>> +			return mem_convert_double_to_string(mem);
>> +		return -1;
>> +	case FIELD_TYPE_DOUBLE:
>> +		if ((mem->flags & MEM_Real) != 0)
>> +			return 0;
>> +		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
>> +			return mem_convert_integer_to_double(mem);
>> +		if ((mem->flags & MEM_Str) != 0)
>> +			return mem_convert_varstring_to_double(mem);
>> +		return -1;
>> +	case FIELD_TYPE_INTEGER:
>> +		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
>> +			return 0;
>> +		if ((mem->flags & MEM_Str) != 0)
>> +			return mem_convert_varstring_to_integer(mem);
>> +		if (mem_is_double(mem))
>> +			return mem_convert_double_to_integer_lossless(mem);
>> +		return -1;
>> +	case FIELD_TYPE_BOOLEAN:
>> +		if ((mem->flags & MEM_Bool) != 0)
>> +			return 0;
>> +		return -1;
>> +	case FIELD_TYPE_VARBINARY:
>> +		if ((mem->flags & MEM_Blob) != 0)
>> +			return 0;
>> +		return -1;
>> +	case FIELD_TYPE_NUMBER:
>> +		if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
>> +			return 0;
>> +		if ((mem->flags & MEM_Str) != 0)
>> +			return mem_convert_to_number(mem);
>> +		return -1;
>> +	case FIELD_TYPE_MAP:
>> +		if (mem_is_map(mem))
>> +			return 0;
>> +		return -1;
>> +	case FIELD_TYPE_ARRAY:
>> +		if (mem_is_array(mem))
>> +			return 0;
>> +		return -1;
>> +	case FIELD_TYPE_SCALAR:
>> +		if ((mem->flags & MEM_Blob) != 0 &&
>> +		    (mem->flags & MEM_Subtype) != 0)
>> +			return -1;
>> +		return 0;
>> +	default:
>> +		break;
>> +	}
>> +	return -1;
>> +}


New patch:

commit 1e61942aa006bb53457006df9b80fc9d0e5aab0a
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 12:43:08 2021 +0300

    sql: introduce mem_cast_implicit()
    
    This patch introduces mem_cast_implicit(). This function is used to
    convert a MEM to given type according to implicit cast rules.
    
    Part of #5818

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 45d2d5fe3..64c183411 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -946,6 +946,32 @@ mem_to_str(struct Mem *mem)
 	return -1;
 }
 
+static inline int
+double_to_uint(struct Mem *mem)
+{
+	double d = mem->u.r;
+	if (d >= 0 && d < (double)UINT64_MAX) {
+		mem->u.u = (uint64_t)d;
+		mem->flags = MEM_UInt;
+		mem->field_type = FIELD_TYPE_UNSIGNED;
+		return 0;
+	}
+	return -1;
+}
+
+static inline int
+double_to_uint_precise(struct Mem *mem)
+{
+	double d = mem->u.r;
+	if (d >= 0 && d < (double)UINT64_MAX && (double)(uint64_t)d == d) {
+		mem->u.u = (uint64_t)d;
+		mem->flags = MEM_UInt;
+		mem->field_type = FIELD_TYPE_UNSIGNED;
+		return 0;
+	}
+	return -1;
+}
+
 static inline int
 bytes_to_uint(struct Mem *mem)
 {
@@ -1071,6 +1097,140 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
 	return -1;
 }
 
+int
+mem_cast_implicit(struct Mem *mem, enum field_type type)
+{
+	if ((mem->flags & MEM_Null) != 0) {
+		mem->field_type = type;
+		return 0;
+	}
+	switch (type) {
+	case FIELD_TYPE_UNSIGNED:
+		if ((mem->flags & MEM_UInt) != 0)
+			return 0;
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_uint(mem);
+		return -1;
+	case FIELD_TYPE_STRING:
+		if ((mem->flags & MEM_Str) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_DOUBLE:
+		if ((mem->flags & MEM_Real) != 0)
+			return 0;
+		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+			return int_to_double(mem);
+		return -1;
+	case FIELD_TYPE_INTEGER:
+		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+			return 0;
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_int(mem);
+		return -1;
+	case FIELD_TYPE_BOOLEAN:
+		if ((mem->flags & MEM_Bool) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_VARBINARY:
+		if ((mem->flags & MEM_Blob) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_NUMBER:
+		if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_MAP:
+		if (mem_is_map(mem))
+			return 0;
+		return -1;
+	case FIELD_TYPE_ARRAY:
+		if (mem_is_array(mem))
+			return 0;
+		return -1;
+	case FIELD_TYPE_SCALAR:
+		if ((mem->flags & MEM_Blob) != 0 &&
+		    (mem->flags & MEM_Subtype) != 0)
+			return -1;
+		return 0;
+	case FIELD_TYPE_ANY:
+		return 0;
+	default:
+		break;
+	}
+	return -1;
+}
+
+int
+mem_cast_implicit_old(struct Mem *mem, enum field_type type)
+{
+	if (mem_is_null(mem))
+		return 0;
+	switch (type) {
+	case FIELD_TYPE_UNSIGNED:
+		if ((mem->flags & MEM_UInt) != 0)
+			return 0;
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_uint_precise(mem);
+		if ((mem->flags & MEM_Str) != 0)
+			return bytes_to_uint(mem);
+		return -1;
+	case FIELD_TYPE_STRING:
+		if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+			return 0;
+		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+			return int_to_str0(mem);
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_str0(mem);
+		return -1;
+	case FIELD_TYPE_DOUBLE:
+		if ((mem->flags & MEM_Real) != 0)
+			return 0;
+		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+			return int_to_double(mem);
+		if ((mem->flags & MEM_Str) != 0)
+			return bin_to_str(mem);
+		return -1;
+	case FIELD_TYPE_INTEGER:
+		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+			return 0;
+		if ((mem->flags & MEM_Str) != 0)
+			return bytes_to_int(mem);
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_int_precise(mem);
+		return -1;
+	case FIELD_TYPE_BOOLEAN:
+		if ((mem->flags & MEM_Bool) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_VARBINARY:
+		if ((mem->flags & MEM_Blob) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_NUMBER:
+		if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
+			return 0;
+		if ((mem->flags & MEM_Str) != 0)
+			return mem_to_number(mem);
+		return -1;
+	case FIELD_TYPE_MAP:
+		if (mem_is_map(mem))
+			return 0;
+		return -1;
+	case FIELD_TYPE_ARRAY:
+		if (mem_is_array(mem))
+			return 0;
+		return -1;
+	case FIELD_TYPE_SCALAR:
+		if ((mem->flags & MEM_Blob) != 0 &&
+		    (mem->flags & MEM_Subtype) != 0)
+			return -1;
+		return 0;
+	default:
+		break;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -2148,122 +2308,6 @@ sqlVdbeMemExpandBlob(Mem * pMem)
 	return 0;
 }
 
-/*
- * Exported version of mem_apply_type(). This one works on sql_value*,
- * not the internal Mem* type.
- */
-void
-sql_value_apply_type(
-	sql_value *pVal,
-	enum field_type type)
-{
-	mem_apply_type((Mem *) pVal, type);
-}
-
-int
-mem_apply_type(struct Mem *record, enum field_type type)
-{
-	if ((record->flags & MEM_Null) != 0)
-		return 0;
-	assert(type < field_type_MAX);
-	switch (type) {
-	case FIELD_TYPE_INTEGER:
-	case FIELD_TYPE_UNSIGNED:
-		if ((record->flags & (MEM_Bool | MEM_Blob)) != 0)
-			return -1;
-		if ((record->flags & MEM_UInt) == MEM_UInt)
-			return 0;
-		if ((record->flags & MEM_Real) == MEM_Real) {
-			double d = record->u.r;
-			if (d >= 0) {
-				if (double_compare_uint64(d, UINT64_MAX,
-							  1) > 0)
-					return 0;
-				if ((double)(uint64_t)d == d) {
-					record->u.u = (uint64_t)d;
-					record->flags = MEM_UInt;
-					record->field_type =
-						FIELD_TYPE_UNSIGNED;
-				}
-			} else {
-				if (double_compare_nint64(d, INT64_MIN, 1) < 0)
-					return 0;
-				if ((double)(int64_t)d == d) {
-					record->u.i = (int64_t)d;
-					record->flags = MEM_Int;
-					record->field_type = FIELD_TYPE_INTEGER;
-				}
-			}
-			return 0;
-		}
-		if ((record->flags & MEM_Str) != 0) {
-			bool is_neg;
-			int64_t i;
-			if (sql_atoi64(record->z, &i, &is_neg, record->n) != 0)
-				return -1;
-			mem_set_int(record, i, is_neg);
-		}
-		if ((record->flags & MEM_Int) == MEM_Int) {
-			if (type == FIELD_TYPE_UNSIGNED)
-				return -1;
-			return 0;
-		}
-		return 0;
-	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 | MEM_UInt)) != 0)
-			return 0;
-		return mem_to_double(record);
-	case FIELD_TYPE_DOUBLE:
-		if ((record->flags & MEM_Real) != 0)
-			return 0;
-		return mem_to_double(record);
-	case FIELD_TYPE_STRING:
-		/*
-		 * Only attempt the conversion to TEXT if there is
-		 * an integer or real representation (BLOB and
-		 * NULL do not get converted).
-		 */
-		if ((record->flags & MEM_Str) == 0 &&
-		    (record->flags & (MEM_Real | MEM_Int | MEM_UInt)) != 0)
-			mem_to_str(record);
-		record->flags &= ~(MEM_Real | MEM_Int | MEM_UInt);
-		return 0;
-	case FIELD_TYPE_VARBINARY:
-		if ((record->flags & MEM_Blob) == 0)
-			return -1;
-		return 0;
-	case FIELD_TYPE_SCALAR:
-		/* Can't cast MAP and ARRAY to scalar types. */
-		if ((record->flags & MEM_Subtype) != 0 &&
-		    record->subtype == SQL_SUBTYPE_MSGPACK) {
-			assert(mp_typeof(*record->z) == MP_MAP ||
-			       mp_typeof(*record->z) == MP_ARRAY);
-			return -1;
-		}
-		return 0;
-	case FIELD_TYPE_MAP:
-		if ((record->flags & MEM_Subtype) != 0 &&
-		    record->subtype == SQL_SUBTYPE_MSGPACK &&
-		    mp_typeof(*record->z) == MP_MAP)
-			return 0;
-		return -1;
-	case FIELD_TYPE_ARRAY:
-		if ((record->flags & MEM_Subtype) != 0 &&
-		    record->subtype == SQL_SUBTYPE_MSGPACK &&
-		    mp_typeof(*record->z) == MP_ARRAY)
-			return 0;
-		return -1;
-	case FIELD_TYPE_ANY:
-		return 0;
-	default:
-		return -1;
-	}
-}
-
 static int
 sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
 {
@@ -2683,14 +2727,6 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
 	return res;
 }
 
-bool
-mem_is_type_compatible(struct Mem *mem, enum field_type type)
-{
-	enum mp_type mp_type = mem_mp_type(mem);
-	assert(mp_type < MP_EXT);
-	return field_mp_plain_type_is_compatible(type, mp_type, true);
-}
-
 int
 sql_vdbemem_finalize(struct Mem *mem, struct func *func)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 73d2ffd6a..dbd58e1a5 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -526,6 +526,16 @@ mem_to_str0(struct Mem *mem);
 int
 mem_cast_explicit(struct Mem *mem, enum field_type type);
 
+/** Convert the given MEM to given type according to implicit cast rules. */
+int
+mem_cast_implicit(struct Mem *mem, enum field_type type);
+
+/**
+ * Convert the given MEM to given type according to legacy implicit cast rules.
+ */
+int
+mem_cast_implicit_old(struct Mem *mem, enum field_type type);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -562,44 +572,6 @@ registerTrace(int iReg, Mem *p);
 int sqlVdbeMemNulTerminate(struct Mem *);
 int sqlVdbeMemExpandBlob(struct Mem *);
 #define ExpandBlob(P) (mem_is_zerobin(P)? sqlVdbeMemExpandBlob(P) : 0)
-void sql_value_apply_type(struct Mem *val, enum field_type type);
-
-
-/**
- * Processing is determined by the field type parameter:
- *
- * INTEGER:
- *    If memory holds floating point value and it can be
- *    converted without loss (2.0 - > 2), it's type is
- *    changed to INT. Otherwise, simply return success status.
- *
- * NUMBER:
- *    If memory holds INT or floating point value,
- *    no actions take place.
- *
- * STRING:
- *    Convert mem to a string representation.
- *
- * SCALAR:
- *    Mem is unchanged, but flag is set to BLOB in case of
- *    scalar-like type. Otherwise, (MAP, ARRAY) conversion
- *    is impossible.
- *
- * BOOLEAN:
- *    If memory holds BOOLEAN no actions take place.
- *
- * ANY:
- *    Mem is unchanged, no actions take place.
- *
- * MAP/ARRAY:
- *    These types can't be casted to scalar ones, or to each
- *    other. So the only valid conversion is to type itself.
- *
- * @param record The value to apply type to.
- * @param type The type to be applied.
- */
-int
-mem_apply_type(struct Mem *record, enum field_type type);
 
 /** Setters = Change MEM value. */
 
@@ -657,17 +629,6 @@ int sqlVdbeMemTooBig(Mem *);
 
 int sqlMemCompare(const Mem *, const Mem *, const struct coll *);
 
-/**
- * Check that MEM_type of the mem is compatible with given type.
- *
- * @param mem The MEM that contains the value to check.
- * @param type The type to check.
- * @retval TRUE if the MEM_type of the value and the given type
- *         are compatible, FALSE otherwise.
- */
-bool
-mem_is_type_compatible(struct Mem *mem, enum field_type type);
-
 /** MEM manipulate functions. */
 
 /**
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 29634841a..72a84e80d 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2092,23 +2092,12 @@ case OP_ApplyType: {
 	while((type = *(types++)) != field_type_MAX) {
 		assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
 		assert(memIsValid(pIn1));
-		if (!mem_is_type_compatible(pIn1, type)) {
-			/* Implicit cast is allowed only to numeric type. */
-			if (!sql_type_is_numeric(type))
-				goto type_mismatch;
-			/* Implicit cast is allowed only from numeric type. */
-			if (!mem_is_num(pIn1))
-				goto type_mismatch;
-			/* Try to convert numeric-to-numeric. */
-			if (mem_cast_explicit(pIn1, type) != 0)
-				goto type_mismatch;
+		if (mem_cast_implicit(pIn1, type) != 0) {
+			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+				 mem_str(pIn1), field_type_strs[type]);
+			goto abort_due_to_error;
 		}
 		pIn1++;
-		continue;
-type_mismatch:
-		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(pIn1), field_type_strs[type]);
-		goto abort_due_to_error;
 	}
 	break;
 }
@@ -2167,7 +2156,7 @@ case OP_MakeRecord: {
 	if (types != NULL) {
 		pRec = pData0;
 		do {
-			mem_apply_type(pRec++, *(types++));
+			mem_cast_implicit_old(pRec++, *(types++));
 		} while(types[0] != field_type_MAX);
 	}
 
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index a5e78be06..672213775 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -2334,7 +2334,7 @@ sqlVdbeGetBoundValue(Vdbe * v, int iVar, u8 aff)
 			sql_value *pRet = sqlValueNew(v->db);
 			if (pRet) {
 				mem_copy(pRet, pMem);
-				sql_value_apply_type(pRet, aff);
+				mem_cast_implicit_old(pRet, aff);
 			}
 			return pRet;
 		}

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

* [Tarantool-patches] [PATCH v5 45/52] sql: introduce mem_get_int()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
                   ` (3 preceding siblings ...)
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 44/52] sql: introduce mem_cast_implicit() Mergen Imeev via Tarantool-patches
@ 2021-04-09 20:53 ` Mergen Imeev via Tarantool-patches
  2021-04-13 23:01   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 46/52] sql: introduce mem_get_uint() Mergen Imeev via Tarantool-patches
                   ` (6 subsequent siblings)
  11 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 20:53 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

Thank you for the review! My answers and new patch below.


On 30.03.2021 02:08, Vladislav Shpilevoy wrote:
> Thanks for the patch!
>
>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index b644c39d8..0fa0f6ac7 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -1532,10 +1543,11 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
>>  static void
>>  zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
>>  {
>> -	i64 n;
>> +	int64_t n;
>>  	assert(argc == 1);
>>  	UNUSED_PARAMETER(argc);
>> -	n = sql_value_int64(argv[0]);
>> +	bool unused;
>> +	mem_get_integer(argv[0], &n, &unused);
>
> The flag is never used anywhere except one assertion where you can
> check the integer value instead. I think you can drop this out
> parameter. In future we could add mem_get_int_with_sign() or something
> like that if necessary.
I think the problem here mostly because most of built-in functions and bitwise
operations cannot work with our INTEGER. They can only work with int64. I
believe, if we fix this problem, there will be no problems with having this
flag.


New patch:

commit 921ae5a9b533bd897100dc2e7923347638719b13
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 13:20:37 2021 +0300

    sql: introduce mem_get_int()
    
    This patch introduces mem_get_int() function. This function is used to
    receive integer value from MEM. If value of MEM is not integer, it is
    converted to integer if possible. MEM is not changed.
    
    Part of #5818

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 0282aec74..701e77d49 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -205,7 +205,9 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 		break;
 	}
 	case MP_INT: {
-		int64_t value = sql_value_int64(argv[0]);
+		bool unused;
+		int64_t value;
+		mem_get_int(argv[0], &value, &unused);
 		assert(value < 0);
 		sql_result_uint(context, -value);
 		break;
@@ -421,7 +423,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	const unsigned char *z2;
 	int len;
 	int p0type;
-	i64 p1, p2;
+	int64_t p1, p2;
 	int negP2 = 0;
 
 	if (argc != 2 && argc != 3) {
@@ -433,7 +435,8 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	if (mem_is_null(argv[1]) || (argc == 3 && mem_is_null(argv[2])))
 		return;
 	p0type = sql_value_type(argv[0]);
-	p1 = sql_value_int(argv[1]);
+	bool unused;
+	mem_get_int(argv[1], &p1, &unused);
 	if (p0type == MP_BIN) {
 		len = sql_value_bytes(argv[0]);
 		z = sql_value_blob(argv[0]);
@@ -449,7 +452,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
 	}
 	if (argc == 3) {
-		p2 = sql_value_int(argv[2]);
+		mem_get_int(argv[2], &p2, &unused);
 		if (p2 < 0) {
 			p2 = -p2;
 			negP2 = 1;
@@ -520,7 +523,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 static void
 roundFunc(sql_context * context, int argc, sql_value ** argv)
 {
-	int n = 0;
+	int64_t n = 0;
 	double r;
 	if (argc != 1 && argc != 2) {
 		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROUND",
@@ -531,7 +534,8 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 	if (argc == 2) {
 		if (mem_is_null(argv[1]))
 			return;
-		n = sql_value_int(argv[1]);
+		bool unused;
+		mem_get_int(argv[1], &n, &unused);
 		if (n < 0)
 			n = 0;
 	}
@@ -674,7 +678,7 @@ randomFunc(sql_context * context, int NotUsed, sql_value ** NotUsed2)
 static void
 randomBlob(sql_context * context, int argc, sql_value ** argv)
 {
-	int n;
+	int64_t n;
 	unsigned char *p;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
@@ -684,7 +688,8 @@ randomBlob(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	n = sql_value_int(argv[0]);
+	bool unused;
+	mem_get_int(argv[0], &n, &unused);
 	if (n < 1)
 		return;
 	p = contextMalloc(context, n);
@@ -1225,10 +1230,11 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
 static void
 zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
 {
-	i64 n;
+	int64_t n;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	n = sql_value_int64(argv[0]);
+	bool unused;
+	mem_get_int(argv[0], &n, &unused);
 	if (n < 0)
 		n = 0;
 	if (sql_result_zeroblob64(context, n) != 0) {
@@ -1472,9 +1478,9 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
 	int input_str_sz = sql_value_bytes(arg2);
 	if (sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT) {
 		uint8_t len_one = 1;
-		trim_procedure(context, sql_value_int(arg1),
-			       (const unsigned char *) " ", &len_one, 1,
-			       input_str, input_str_sz);
+		uint64_t n = sql_value_uint64(arg1);
+		trim_procedure(context, n, (const unsigned char *) " ",
+			       &len_one, 1, input_str, input_str_sz);
 	} else if ((trim_set = sql_value_text(arg1)) != NULL) {
 		int trim_set_sz = sql_value_bytes(arg1);
 		uint8_t *char_len;
@@ -1512,7 +1518,8 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
 					     &char_len);
 	if (char_cnt == -1)
 		return;
-	trim_procedure(context, sql_value_int(arg1), trim_set, char_len,
+	uint64_t n = sql_value_uint64(arg1);
+	trim_procedure(context, n, trim_set, char_len,
 		       char_cnt, input_str, input_str_sz);
 	sql_free(char_len);
 }
@@ -1651,7 +1658,9 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 	}
 	p->cnt++;
 	if (type == MP_INT || type == MP_UINT) {
-		int64_t v = sql_value_int64(argv[0]);
+		bool unused;
+		int64_t v;
+		mem_get_int(argv[0], &v, &unused);
 		if (type == MP_INT)
 			p->rSum += v;
 		else
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 64c183411..259bb7d2c 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1231,6 +1231,38 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type)
 	return -1;
 }
 
+int
+mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
+{
+	if ((mem->flags & MEM_Int) != 0) {
+		*i = mem->u.i;
+		*is_neg = true;
+		return 0;
+	}
+	if ((mem->flags & MEM_UInt) != 0) {
+		*i = mem->u.i;
+		*is_neg = false;
+		return 0;
+	}
+	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+		return sql_atoi64(mem->z, i, is_neg, mem->n);
+	if ((mem->flags & MEM_Real) != 0) {
+		double d = mem->u.r;
+		if (d < 0 && d >= (double)INT64_MIN) {
+			*i = (int64_t)d;
+			*is_neg = true;
+			return 0;
+		}
+		if (d >= 0 && d < (double)UINT64_MAX) {
+			*i = (int64_t)(uint64_t)d;
+			*is_neg = false;
+			return 0;
+		}
+		return -1;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -1593,12 +1625,12 @@ bitwise_prepare(const struct Mem *left, const struct Mem *right,
 		int64_t *a, int64_t *b)
 {
 	bool unused;
-	if (sqlVdbeIntValue(left, a, &unused) != 0) {
+	if (mem_get_int(left, a, &unused) != 0) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(left),
 			 "integer");
 		return -1;
 	}
-	if (sqlVdbeIntValue(right, b, &unused) != 0) {
+	if (mem_get_int(right, b, &unused) != 0) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(right),
 			 "integer");
 		return -1;
@@ -1687,7 +1719,7 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
 		return 0;
 	int64_t i;
 	bool unused;
-	if (sqlVdbeIntValue(mem, &i, &unused) != 0) {
+	if (mem_get_int(mem, &i, &unused) != 0) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(mem),
 			 "integer");
 		return -1;
@@ -1932,35 +1964,6 @@ valueToText(sql_value * pVal)
 	return pVal->z;
 }
 
-/*
- * Convert a 64-bit IEEE double into a 64-bit signed integer.
- * If the double is out of range of a 64-bit signed integer then
- * return the closest available 64-bit signed integer.
- */
-static int
-doubleToInt64(double r, int64_t *i)
-{
-	/*
-	 * Many compilers we encounter do not define constants for the
-	 * minimum and maximum 64-bit integers, or they define them
-	 * inconsistently.  And many do not understand the "LL" notation.
-	 * So we define our own static constants here using nothing
-	 * larger than a 32-bit integer constant.
-	 */
-	static const int64_t maxInt = LARGEST_INT64;
-	static const int64_t minInt = SMALLEST_INT64;
-	if (r <= (double)minInt) {
-		*i = minInt;
-		return -1;
-	} else if (r >= (double)maxInt) {
-		*i = maxInt;
-		return -1;
-	} else {
-		*i = (int64_t) r;
-		return *i != r;
-	}
-}
-
 /*
  * It is already known that pMem contains an unterminated string.
  * Add the zero terminator.
@@ -2434,42 +2437,6 @@ mem_value_bool(const struct Mem *mem, bool *b)
 	return -1;
 }
 
-/*
- * Return some kind of integer value which is the best we can do
- * at representing the value that *pMem describes as an integer.
- * If pMem is an integer, then the value is exact.  If pMem is
- * a floating-point then the value returned is the integer part.
- * If pMem is a string or blob, then we make an attempt to convert
- * it into an integer and return that.  If pMem represents an
- * an SQL-NULL value, return 0.
- *
- * If pMem represents a string value, its encoding might be changed.
- */
-int
-sqlVdbeIntValue(const struct Mem *pMem, int64_t *i, bool *is_neg)
-{
-	int flags;
-	assert(EIGHT_BYTE_ALIGNMENT(pMem));
-	flags = pMem->flags;
-	if (flags & MEM_Int) {
-		*i = pMem->u.i;
-		*is_neg = true;
-		return 0;
-	} else if (flags & MEM_UInt) {
-		*i = pMem->u.u;
-		*is_neg = false;
-		return 0;
-	} else if (flags & MEM_Real) {
-		*is_neg = pMem->u.r < 0;
-		return doubleToInt64(pMem->u.r, i);
-	} else if (flags & (MEM_Str)) {
-		assert(pMem->z || pMem->n == 0);
-		if (sql_atoi64(pMem->z, i, is_neg, pMem->n) == 0)
-			return 0;
-	}
-	return -1;
-}
-
 /*
  * Return the best representation of pMem that we can get into a
  * double.  If pMem is already a double or an integer, return its
@@ -2540,30 +2507,12 @@ sql_value_boolean(sql_value *val)
 	return b;
 }
 
-int
-sql_value_int(sql_value * pVal)
-{
-	int64_t i = 0;
-	bool is_neg;
-	sqlVdbeIntValue((Mem *) pVal, &i, &is_neg);
-	return (int)i;
-}
-
-sql_int64
-sql_value_int64(sql_value * pVal)
-{
-	int64_t i = 0;
-	bool unused;
-	sqlVdbeIntValue((Mem *) pVal, &i, &unused);
-	return i;
-}
-
 uint64_t
 sql_value_uint64(sql_value *val)
 {
 	int64_t i = 0;
 	bool is_neg;
-	sqlVdbeIntValue((struct Mem *) val, &i, &is_neg);
+	mem_get_int((struct Mem *) val, &i, &is_neg);
 	assert(!is_neg);
 	return i;
 }
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index dbd58e1a5..2b5385c55 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -536,6 +536,14 @@ mem_cast_implicit(struct Mem *mem, enum field_type type);
 int
 mem_cast_implicit_old(struct Mem *mem, enum field_type type);
 
+/**
+ * Return value for MEM of INTEGER type. For MEM of all other types convert
+ * value of the MEM to INTEGER if possible and return converted value. Original
+ * MEM is not changed.
+ */
+int
+mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -590,7 +598,6 @@ releaseMemArray(Mem * p, int N);
 
 int
 mem_value_bool(const struct Mem *mem, bool *b);
-int sqlVdbeIntValue(const struct Mem *, int64_t *, bool *is_neg);
 int sqlVdbeRealValue(struct Mem *, double *);
 const void *
 sql_value_blob(struct Mem *);
@@ -604,12 +611,6 @@ sql_value_double(struct Mem *);
 bool
 sql_value_boolean(struct Mem *val);
 
-int
-sql_value_int(struct Mem *);
-
-sql_int64
-sql_value_int64(struct Mem *);
-
 uint64_t
 sql_value_uint64(struct Mem *val);
 
diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
index cf32ba3f3..09da39e81 100644
--- a/src/box/sql/printf.c
+++ b/src/box/sql/printf.c
@@ -144,7 +144,10 @@ getIntArg(PrintfArguments * p)
 {
 	if (p->nArg <= p->nUsed)
 		return 0;
-	return sql_value_int64(p->apArg[p->nUsed++]);
+	int64_t i;
+	bool unused;
+	mem_get_int(p->apArg[p->nUsed++], &i, &unused);
+	return (sql_int64)i;
 }
 
 static double
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 7a026d21b..0af247ebf 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -445,15 +445,9 @@ sql_column_bytes16(sql_stmt *, int iCol);
 double
 sql_column_double(sql_stmt *, int iCol);
 
-int
-sql_column_int(sql_stmt *, int iCol);
-
 bool
 sql_column_boolean(struct sql_stmt *stmt, int column);
 
-sql_int64
-sql_column_int64(sql_stmt *, int iCol);
-
 uint64_t
 sql_column_uint64(struct sql_stmt *stmt, int column);
 
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index c2d4b8b8a..40404a3b7 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -490,24 +490,12 @@ sql_column_double(sql_stmt * pStmt, int i)
 	return sql_value_double(columnMem(pStmt, i));
 }
 
-int
-sql_column_int(sql_stmt * pStmt, int i)
-{
-	return sql_value_int(columnMem(pStmt, i));
-}
-
 bool
 sql_column_boolean(struct sql_stmt *stmt, int i)
 {
 	return sql_value_boolean(columnMem(stmt, i));
 }
 
-sql_int64
-sql_column_int64(sql_stmt * pStmt, int i)
-{
-	return sql_value_int64(columnMem(pStmt, i));
-}
-
 uint64_t
 sql_column_uint64(sql_stmt * pStmt, int i)
 {

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

* [Tarantool-patches] [PATCH v5 46/52] sql: introduce mem_get_uint()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
                   ` (4 preceding siblings ...)
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 45/52] sql: introduce mem_get_int() Mergen Imeev via Tarantool-patches
@ 2021-04-09 21:08 ` Mergen Imeev via Tarantool-patches
  2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 47/52] sql: introduce mem_get_double() Mergen Imeev via Tarantool-patches
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 21:08 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

Thank you for the review! My answer and new patch below.


On 30.03.2021 02:08, Vladislav Shpilevoy wrote:
> Thanks for the patch!
>
> On 23.03.2021 10:36, Mergen Imeev via Tarantool-patches wrote:
>> This patch introduces mem_get_unsigned() function which is used to
>> receive unsigned value from MEM.
>>
>> Part of #5818
>> ---
>>  src/box/sql/func.c    | 16 +++++++++++-----
>>  src/box/sql/mem.c     | 37 +++++++++++++++++++++++++++----------
>>  src/box/sql/mem.h     |  6 +++---
>>  src/box/sql/sqlInt.h  |  3 ---
>>  src/box/sql/vdbeapi.c |  6 ------
>>  5 files changed, 41 insertions(+), 27 deletions(-)
>>
>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index 0fa0f6ac7..a851d98f2 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -118,9 +118,12 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
>>  			luaL_pushint64(L, n);
>>  			break;
>>  		}
>> -		case MP_UINT:
>> -			luaL_pushuint64(L, sql_value_uint64(param));
>> +		case MP_UINT: {
>> +			uint64_t u;
>> +			mem_get_unsigned(param, &u);
>> +			luaL_pushuint64(L, u);
> Maybe we could make 2 functions? One to get the value and ignore
> the errors, and the other to get as an out parameter + return an
> error?
>
> For instance, mem_to_uint() - returns uint64_t and internally asserts
> that the value is correct. And mem_get_uint() works like your version.
>
> The same for the other get functions whose result is often ignored.
For some functions I created a "proxy" functions in func.c the way you
described, but not for this function since it is only used in a few places of
sql/func.c. Should I do this for all functions? In func.c I mean. I see this as
temporary measure, since I hope we will rework built-in functions one day.


New patch:

commit bdd3107a42b82a0e1f964819fa0bf4798fd87a4e
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 13:55:37 2021 +0300

    sql: introduce mem_get_uint()
    
    This patch introduces mem_get_uint() function. This function is used to
    receive unsigned value from MEM. If value of MEM is not unsigned, it is
    converted to unsigned if possible. MEM is not changed.
    
    Part of #5818

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 701e77d49..f2254fb29 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -201,7 +201,9 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 	UNUSED_PARAMETER(argc);
 	switch (sql_value_type(argv[0])) {
 	case MP_UINT: {
-		sql_result_uint(context, sql_value_uint64(argv[0]));
+		uint64_t u;
+		mem_get_uint(argv[0], &u);
+		sql_result_uint(context, u);
 		break;
 	}
 	case MP_INT: {
@@ -1174,7 +1176,7 @@ charFunc(sql_context * context, int argc, sql_value ** argv)
 		if (sql_value_type(argv[i]) == MP_INT)
 			x = 0xfffd;
 		else
-			x = sql_value_uint64(argv[i]);
+			mem_get_uint(argv[i], &x);
 		if (x > 0x10ffff)
 			x = 0xfffd;
 		c = (unsigned)(x & 0x1fffff);
@@ -1478,7 +1480,8 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
 	int input_str_sz = sql_value_bytes(arg2);
 	if (sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT) {
 		uint8_t len_one = 1;
-		uint64_t n = sql_value_uint64(arg1);
+		uint64_t n;
+		mem_get_uint(arg1, &n);
 		trim_procedure(context, n, (const unsigned char *) " ",
 			       &len_one, 1, input_str, input_str_sz);
 	} else if ((trim_set = sql_value_text(arg1)) != NULL) {
@@ -1518,7 +1521,8 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
 					     &char_len);
 	if (char_cnt == -1)
 		return;
-	uint64_t n = sql_value_uint64(arg1);
+	uint64_t n;
+	mem_get_uint(arg1, &n);
 	trim_procedure(context, n, trim_set, char_len,
 		       char_cnt, input_str, input_str_sz);
 	sql_free(char_len);
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 259bb7d2c..136da9d5d 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1263,6 +1263,33 @@ mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
 	return -1;
 }
 
+int
+mem_get_uint(const struct Mem *mem, uint64_t *u)
+{
+	if ((mem->flags & MEM_Int) != 0)
+		return -1;
+	if ((mem->flags & MEM_UInt) != 0) {
+		*u = mem->u.u;
+		return 0;
+	}
+	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
+		bool is_neg;
+		if (sql_atoi64(mem->z, (int64_t *)u, &is_neg, mem->n) != 0 ||
+		    is_neg)
+			return -1;
+		return 0;
+	}
+	if ((mem->flags & MEM_Real) != 0) {
+		double d = mem->u.r;
+		if (d >= 0 && d < (double)UINT64_MAX) {
+			*u = (uint64_t)d;
+			return 0;
+		}
+		return -1;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -2507,16 +2534,6 @@ sql_value_boolean(sql_value *val)
 	return b;
 }
 
-uint64_t
-sql_value_uint64(sql_value *val)
-{
-	int64_t i = 0;
-	bool is_neg;
-	mem_get_int((struct Mem *) val, &i, &is_neg);
-	assert(!is_neg);
-	return i;
-}
-
 const unsigned char *
 sql_value_text(sql_value * pVal)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 2b5385c55..87aee0e9b 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -544,6 +544,14 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type);
 int
 mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg);
 
+/**
+ * Return value for MEM of UNSIGNED type. For MEM of all other types convert
+ * value of the MEM to UNSIGNED if possible and return converted value. Original
+ * MEM is not changed.
+ */
+int
+mem_get_uint(const struct Mem *mem, uint64_t *u);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -611,9 +619,6 @@ sql_value_double(struct Mem *);
 bool
 sql_value_boolean(struct Mem *val);
 
-uint64_t
-sql_value_uint64(struct Mem *val);
-
 const unsigned char *
 sql_value_text(struct Mem *);
 
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 0af247ebf..6ead9b261 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -448,9 +448,6 @@ sql_column_double(sql_stmt *, int iCol);
 bool
 sql_column_boolean(struct sql_stmt *stmt, int column);
 
-uint64_t
-sql_column_uint64(struct sql_stmt *stmt, int column);
-
 const unsigned char *
 sql_column_text(sql_stmt *,
 		    int iCol);
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 40404a3b7..85bbbc420 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -496,12 +496,6 @@ sql_column_boolean(struct sql_stmt *stmt, int i)
 	return sql_value_boolean(columnMem(stmt, i));
 }
 
-uint64_t
-sql_column_uint64(sql_stmt * pStmt, int i)
-{
-	return sql_value_uint64(columnMem(pStmt, i));
-}
-
 const unsigned char *
 sql_column_text(sql_stmt * pStmt, int i)
 {

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

* [Tarantool-patches] [PATCH v5 47/52] sql: introduce mem_get_double()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
                   ` (5 preceding siblings ...)
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 46/52] sql: introduce mem_get_uint() Mergen Imeev via Tarantool-patches
@ 2021-04-09 21:08 ` Mergen Imeev via Tarantool-patches
  2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 48/52] sql: introduce mem_get_bool() Mergen Imeev via Tarantool-patches
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 21:08 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

This patch introduces mem_get_double(). This function is used to receive
double value from MEM. If value of MEM is not double, it is converted to
double if possible. MEM is not changed.

Part of #5818
---
 src/box/sql/func.c    | 17 +++++++------
 src/box/sql/mem.c     | 57 +++++++++++++++++--------------------------
 src/box/sql/mem.h     | 12 ++++++---
 src/box/sql/printf.c  |  4 ++-
 src/box/sql/sqlInt.h  |  3 ---
 src/box/sql/vdbeapi.c |  6 -----
 6 files changed, 44 insertions(+), 55 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index f2254fb29..aeab06e2a 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -229,12 +229,13 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 		return;
 	}
 	default:{
-			/* Because sql_value_double() returns 0.0 if the argument is not
-			 * something that can be converted into a number, we have:
-			 * IMP: R-01992-00519 Abs(X) returns 0.0 if X is a string or blob
+			/*
+			 * Abs(X) returns 0.0 if X is a string or blob
 			 * that cannot be converted to a numeric value.
 			 */
-			double rVal = sql_value_double(argv[0]);
+			double rVal;
+			if (mem_get_double(argv[0], &rVal) != 0)
+				rVal = 0;
 			if (rVal < 0)
 				rVal = -rVal;
 			sql_result_double(context, rVal);
@@ -549,7 +550,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	r = sql_value_double(argv[0]);
+	mem_get_double(argv[0], &r);
 	/* If Y==0 and X will fit in a 64-bit int,
 	 * handle the rounding directly,
 	 * otherwise use printf.
@@ -1056,7 +1057,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_DOUBLE:{
 			double r1, r2;
 			char zBuf[50];
-			r1 = sql_value_double(argv[0]);
+			mem_get_double(argv[0], &r1);
 			sql_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
 			sqlAtoF(zBuf, &r2, 20);
 			if (r1 != r2) {
@@ -1675,7 +1676,9 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 			p->overflow = 1;
 		}
 	} else {
-		p->rSum += sql_value_double(argv[0]);
+		double d;
+		mem_get_double(argv[0], &d);
+		p->rSum += d;
 		p->approx = 1;
 	}
 }
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 136da9d5d..930c44bec 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1290,6 +1290,29 @@ mem_get_uint(const struct Mem *mem, uint64_t *u)
 	return -1;
 }
 
+int
+mem_get_double(const struct Mem *mem, double *d)
+{
+	if ((mem->flags & MEM_Real) != 0) {
+		*d = mem->u.r;
+		return 0;
+	}
+	if ((mem->flags & MEM_Int) != 0) {
+		*d = (double)mem->u.i;
+		return 0;
+	}
+	if ((mem->flags & MEM_UInt) != 0) {
+		*d = (double)mem->u.u;
+		return 0;
+	}
+	if ((mem->flags & MEM_Str) != 0) {
+		if (sqlAtoF(mem->z, d, mem->n) == 0)
+			return -1;
+		return 0;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -2464,32 +2487,6 @@ mem_value_bool(const struct Mem *mem, bool *b)
 	return -1;
 }
 
-/*
- * Return the best representation of pMem that we can get into a
- * double.  If pMem is already a double or an integer, return its
- * value.  If it is a string or blob, try to convert it to a double.
- * If it is a NULL, return 0.0.
- */
-int
-sqlVdbeRealValue(Mem * pMem, double *v)
-{
-	assert(EIGHT_BYTE_ALIGNMENT(pMem));
-	if (pMem->flags & MEM_Real) {
-		*v = pMem->u.r;
-		return 0;
-	} else if (pMem->flags & MEM_Int) {
-		*v = (double)pMem->u.i;
-		return 0;
-	} else if ((pMem->flags & MEM_UInt) != 0) {
-		*v = (double)pMem->u.u;
-		return 0;
-	} else if (pMem->flags & MEM_Str) {
-		if (sqlAtoF(pMem->z, v, pMem->n))
-			return 0;
-	}
-	return -1;
-}
-
 /**************************** sql_value_  ******************************
  * The following routines extract information from a Mem or sql_value
  * structure.
@@ -2516,14 +2513,6 @@ sql_value_bytes(sql_value * pVal)
 	return sqlValueBytes(pVal);
 }
 
-double
-sql_value_double(sql_value * pVal)
-{
-	double v = 0.0;
-	sqlVdbeRealValue((Mem *) pVal, &v);
-	return v;
-}
-
 bool
 sql_value_boolean(sql_value *val)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 87aee0e9b..34b0b3f13 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -552,6 +552,14 @@ mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg);
 int
 mem_get_uint(const struct Mem *mem, uint64_t *u);
 
+/**
+ * Return value for MEM of DOUBLE type. For MEM of all other types convert
+ * value of the MEM to DOUBLE if possible and return converted value. Original
+ * MEM is not changed.
+ */
+int
+mem_get_double(const struct Mem *mem, double *d);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -606,16 +614,12 @@ releaseMemArray(Mem * p, int N);
 
 int
 mem_value_bool(const struct Mem *mem, bool *b);
-int sqlVdbeRealValue(struct Mem *, double *);
 const void *
 sql_value_blob(struct Mem *);
 
 int
 sql_value_bytes(struct Mem *);
 
-double
-sql_value_double(struct Mem *);
-
 bool
 sql_value_boolean(struct Mem *val);
 
diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
index 09da39e81..605478820 100644
--- a/src/box/sql/printf.c
+++ b/src/box/sql/printf.c
@@ -155,7 +155,9 @@ getDoubleArg(PrintfArguments * p)
 {
 	if (p->nArg <= p->nUsed)
 		return 0.0;
-	return sql_value_double(p->apArg[p->nUsed++]);
+	double d;
+	mem_get_double(p->apArg[p->nUsed++], &d);
+	return d;
 }
 
 static char *
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 6ead9b261..3f6fa1722 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -442,9 +442,6 @@ sql_column_bytes(sql_stmt *, int iCol);
 int
 sql_column_bytes16(sql_stmt *, int iCol);
 
-double
-sql_column_double(sql_stmt *, int iCol);
-
 bool
 sql_column_boolean(struct sql_stmt *stmt, int column);
 
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 85bbbc420..b1a9aed6e 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -484,12 +484,6 @@ sql_column_bytes(sql_stmt * pStmt, int i)
 	return sql_value_bytes(columnMem(pStmt, i));
 }
 
-double
-sql_column_double(sql_stmt * pStmt, int i)
-{
-	return sql_value_double(columnMem(pStmt, i));
-}
-
 bool
 sql_column_boolean(struct sql_stmt *stmt, int i)
 {
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v5 48/52] sql: introduce mem_get_bool()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
                   ` (6 preceding siblings ...)
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 47/52] sql: introduce mem_get_double() Mergen Imeev via Tarantool-patches
@ 2021-04-09 21:08 ` Mergen Imeev via Tarantool-patches
  2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 49/52] sql: introduce mem_get_str0() and mem_as_str0() Mergen Imeev via Tarantool-patches
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 21:08 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

This patch introduces mem_get_bool(). This function is used to receive
boolean value from MEM. If value of MEM is not boolean, it is
converted to boolean if possible. MEM is not changed.

Part of #5818
---
 src/box/sql/func.c    |  5 +++--
 src/box/sql/mem.c     | 30 ++++++++++--------------------
 src/box/sql/mem.h     | 13 ++++++++-----
 src/box/sql/sqlInt.h  |  3 ---
 src/box/sql/vdbeapi.c |  6 ------
 5 files changed, 21 insertions(+), 36 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index aeab06e2a..6a6970647 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1130,8 +1130,9 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 			break;
 		}
 	case MP_BOOL: {
-		sql_result_text(context,
-				SQL_TOKEN_BOOLEAN(sql_value_boolean(argv[0])),
+		bool b;
+		mem_get_bool(argv[0], &b);
+		sql_result_text(context, SQL_TOKEN_BOOLEAN(b),
 				-1, SQL_TRANSIENT);
 		break;
 	}
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 930c44bec..6f787f7cc 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1313,6 +1313,16 @@ mem_get_double(const struct Mem *mem, double *d)
 	return -1;
 }
 
+int
+mem_get_bool(const struct Mem *mem, bool *b)
+{
+	if ((mem->flags & MEM_Bool) != 0) {
+		*b = mem->u.b;
+		return 0;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -2477,16 +2487,6 @@ releaseMemArray(Mem * p, int N)
 	}
 }
 
-int
-mem_value_bool(const struct Mem *mem, bool *b)
-{
-	if ((mem->flags  & MEM_Bool) != 0) {
-		*b = mem->u.b;
-		return 0;
-	}
-	return -1;
-}
-
 /**************************** sql_value_  ******************************
  * The following routines extract information from a Mem or sql_value
  * structure.
@@ -2513,16 +2513,6 @@ sql_value_bytes(sql_value * pVal)
 	return sqlValueBytes(pVal);
 }
 
-bool
-sql_value_boolean(sql_value *val)
-{
-	bool b = false;
-	int rc = mem_value_bool((struct Mem *) val, &b);
-	assert(rc == 0);
-	(void) rc;
-	return b;
-}
-
 const unsigned char *
 sql_value_text(sql_value * pVal)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 34b0b3f13..e48e8788c 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -560,6 +560,14 @@ mem_get_uint(const struct Mem *mem, uint64_t *u);
 int
 mem_get_double(const struct Mem *mem, double *d);
 
+/**
+ * Return value for MEM of BOOLEAN type. For MEM of all other types convert
+ * value of the MEM to BOOLEAN if possible and return converted value. Original
+ * MEM is not changed.
+ */
+int
+mem_get_bool(const struct Mem *mem, bool *b);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -612,17 +620,12 @@ releaseMemArray(Mem * p, int N);
 
 /** Getters. */
 
-int
-mem_value_bool(const struct Mem *mem, bool *b);
 const void *
 sql_value_blob(struct Mem *);
 
 int
 sql_value_bytes(struct Mem *);
 
-bool
-sql_value_boolean(struct Mem *val);
-
 const unsigned char *
 sql_value_text(struct Mem *);
 
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 3f6fa1722..df9469941 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -442,9 +442,6 @@ sql_column_bytes(sql_stmt *, int iCol);
 int
 sql_column_bytes16(sql_stmt *, int iCol);
 
-bool
-sql_column_boolean(struct sql_stmt *stmt, int column);
-
 const unsigned char *
 sql_column_text(sql_stmt *,
 		    int iCol);
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index b1a9aed6e..1d3c23a70 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -484,12 +484,6 @@ sql_column_bytes(sql_stmt * pStmt, int i)
 	return sql_value_bytes(columnMem(pStmt, i));
 }
 
-bool
-sql_column_boolean(struct sql_stmt *stmt, int i)
-{
-	return sql_value_boolean(columnMem(stmt, i));
-}
-
 const unsigned char *
 sql_column_text(sql_stmt * pStmt, int i)
 {
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v5 49/52] sql: introduce mem_get_str0() and mem_as_str0()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
                   ` (7 preceding siblings ...)
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 48/52] sql: introduce mem_get_bool() Mergen Imeev via Tarantool-patches
@ 2021-04-09 21:08 ` Mergen Imeev via Tarantool-patches
  2021-04-13 23:06   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 50/52] sql: introduce mem_get_bin() Mergen Imeev via Tarantool-patches
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 21:08 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

Thank you for the review! My answers and new patch below.


On 30.03.2021 02:08, Vladislav Shpilevoy wrote:
> Thanks for the patch!
>
>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index d033dae86..78f4ec3b5 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -54,6 +54,24 @@
>>  #include "lua/utils.h"
>>  #include "mpstream/mpstream.h"
>>  
>> +static const char *
>> +mem_get_str(struct Mem *mem)
>> +{
>> +  const char *str;
>> +  if (mem_convert_to_string0(mem) != 0 || mem_get_string0(mem, &str) != 0)
>
> 1. You have the same code in 2 other files. I think it is worth moving this
> to mem.h.
>
Done.

>> +    return NULL;
>> +  return str;
>> +}
>> +
>> +static const unsigned char *
>> +mem_get_ustr(struct Mem *mem)
>
> 2. Function called 'get' should not change the value. Maybe 'as'? mem_as_str().
>
Thanks! Fixed.

>> +{
>> +  const char *str;
>> +  if (mem_convert_to_string0(mem) != 0 || mem_get_string0(mem, &str) != 0)
>> +    return NULL;
>> +  return (const unsigned char *)str;
>> +}



New patch:

commit 056987bba3f23e3298201d14728452f3033a166c
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Mar 18 12:41:41 2021 +0300

    sql: introduce mem_get_str0() and mem_as_str0()
    
    This patch introduces mem_get_str0() and mem_as_str0(). Function
    mem_get_str0() is used to receive NULL-terminated string from MEM. If
    value of MEM is not NULL-terminated string, it is converted to
    NULL-terminated string if possible. MEM is not changed. Function
    mem_as_str0() is also used to receive NULL-terminated string from MEM,
    however if MEM does not contain NULL-terminated string it converts MEM
    to MEM that contains NULL-terminated string and returns its value.
    
    Part of #5818

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 6a6970647..c1db6f78a 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -48,6 +48,12 @@
 #include "box/coll_id_cache.h"
 #include "box/schema.h"
 
+static const unsigned char *
+mem_as_ustr(struct Mem *mem)
+{
+ return (const unsigned char *)mem_as_str0(mem);
+}
+
 /*
  * Return the collating function associated with a function.
  */
@@ -174,7 +180,7 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
      break;
    }
  case MP_STR:{
-     const unsigned char *z = sql_value_text(argv[0]);
+     const unsigned char *z = mem_as_ustr(argv[0]);
      if (z == 0)
        return;
      len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
@@ -329,8 +335,8 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
       * Character size is equal to
       * needle char size.
       */
-     haystack_str = sql_value_text(haystack);
-     needle_str = sql_value_text(needle);
+     haystack_str = mem_as_ustr(haystack);
+     needle_str = mem_as_ustr(needle);
 
      int n_needle_chars =
        sql_utf8_char_count(needle_str, n_needle_bytes);
@@ -392,8 +398,7 @@ printfFunc(sql_context * context, int argc, sql_value ** argv)
  int n;
  sql *db = sql_context_db_handle(context);
 
- if (argc >= 1
-     && (zFormat = (const char *)sql_value_text(argv[0])) != 0) {
+ if (argc >= 1 && (zFormat = mem_as_str0(argv[0])) != NULL) {
    x.nArg = argc - 1;
    x.nUsed = 0;
    x.apArg = argv + 1;
@@ -447,7 +452,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
      return;
    assert(len == sql_value_bytes(argv[0]));
  } else {
-   z = sql_value_text(argv[0]);
+   z = mem_as_ustr(argv[0]);
    if (z == 0)
      return;
    len = 0;
@@ -610,13 +615,13 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
    context->is_aborted = true;                                    \
    return;                                                        \
  }                                                                      \
- z2 = (char *)sql_value_text(argv[0]);                              \
+ z2 = mem_as_str0(argv[0]);                                             \
  n = sql_value_bytes(argv[0]);                                      \
  /*                                                                     \
   * Verify that the call to _bytes()                                    \
   * does not invalidate the _text() pointer.                            \
   */                                                                    \
- assert(z2 == (char *)sql_value_text(argv[0]));                     \
+ assert(z2 == mem_as_str0(argv[0]));                                    \
  if (!z2)                                                               \
    return;                                                        \
  z1 = contextMalloc(context, ((i64) n) + 1);                            \
@@ -947,8 +952,8 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
    context->is_aborted = true;
    return;
  }
- const char *zB = (const char *) sql_value_text(argv[0]);
- const char *zA = (const char *) sql_value_text(argv[1]);
+ const char *zB = mem_as_str0(argv[0]);
+ const char *zA = mem_as_str0(argv[1]);
  const char *zB_end = zB + sql_value_bytes(argv[0]);
  const char *zA_end = zA + sql_value_bytes(argv[1]);
 
@@ -967,7 +972,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
    return;
  }
  /* Encoding did not change */
- assert(zB == (const char *) sql_value_text(argv[0]));
+ assert(zB == mem_as_str0(argv[0]));
 
  if (argc == 3) {
    /*
@@ -975,7 +980,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
     * single UTF-8 character. Otherwise, return an
     * error.
     */
-   const unsigned char *zEsc = sql_value_text(argv[2]);
+   const unsigned char *zEsc = mem_as_ustr(argv[2]);
    if (zEsc == 0)
      return;
    if (sql_utf8_char_count(zEsc, sql_value_bytes(argv[2])) != 1) {
@@ -1104,7 +1109,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
  case MP_STR:{
      int i, j;
      u64 n;
-     const unsigned char *zArg = sql_value_text(argv[0]);
+     const unsigned char *zArg = mem_as_ustr(argv[0]);
      char *z;
 
      if (zArg == 0)
@@ -1151,7 +1156,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 static void
 unicodeFunc(sql_context * context, int argc, sql_value ** argv)
 {
- const unsigned char *z = sql_value_text(argv[0]);
+ const unsigned char *z = mem_as_ustr(argv[0]);
  (void)argc;
  if (z && z[0])
    sql_result_uint(context, sqlUtf8Read(&z));
@@ -1270,12 +1275,12 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 
  assert(argc == 3);
  UNUSED_PARAMETER(argc);
- zStr = sql_value_text(argv[0]);
+ zStr = mem_as_ustr(argv[0]);
  if (zStr == 0)
    return;
  nStr = sql_value_bytes(argv[0]);
- assert(zStr == sql_value_text(argv[0]));  /* No encoding change */
- zPattern = sql_value_text(argv[1]);
+ assert(zStr == mem_as_ustr(argv[0])); /* No encoding change */
+ zPattern = mem_as_ustr(argv[1]);
  if (zPattern == 0) {
    assert(mem_is_null(argv[1])
           || sql_context_db_handle(context)->mallocFailed);
@@ -1287,12 +1292,12 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
    sql_result_value(context, argv[0]);
    return;
  }
- assert(zPattern == sql_value_text(argv[1]));  /* No encoding change */
- zRep = sql_value_text(argv[2]);
+ assert(zPattern == mem_as_ustr(argv[1])); /* No encoding change */
+ zRep = mem_as_ustr(argv[2]);
  if (zRep == 0)
    return;
  nRep = sql_value_bytes(argv[2]);
- assert(zRep == sql_value_text(argv[2]));
+ assert(zRep == mem_as_ustr(argv[2]));
  nOut = nStr + 1;
  assert(nOut < SQL_MAX_LENGTH);
  zOut = contextMalloc(context, (i64) nOut);
@@ -1454,7 +1459,7 @@ trim_func_one_arg(struct sql_context *context, sql_value *arg)
  else
    default_trim = (const unsigned char *) " ";
  int input_str_sz = sql_value_bytes(arg);
- const unsigned char *input_str = sql_value_text(arg);
+ const unsigned char *input_str = mem_as_ustr(arg);
  uint8_t trim_char_len[1] = { 1 };
  trim_procedure(context, TRIM_BOTH, default_trim, trim_char_len, 1,
           input_str, input_str_sz);
@@ -1476,7 +1481,7 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
       sql_value *arg2)
 {
  const unsigned char *input_str, *trim_set;
- if ((input_str = sql_value_text(arg2)) == NULL)
+ if ((input_str = mem_as_ustr(arg2)) == NULL)
    return;
 
  int input_str_sz = sql_value_bytes(arg2);
@@ -1486,7 +1491,7 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
    mem_get_uint(arg1, &n);
    trim_procedure(context, n, (const unsigned char *) " ",
             &len_one, 1, input_str, input_str_sz);
- } else if ((trim_set = sql_value_text(arg1)) != NULL) {
+ } else if ((trim_set = mem_as_ustr(arg1)) != NULL) {
    int trim_set_sz = sql_value_bytes(arg1);
    uint8_t *char_len;
    int char_cnt = trim_prepare_char_len(context, trim_set,
@@ -1512,8 +1517,8 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
 {
  assert(sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT);
  const unsigned char *input_str, *trim_set;
- if ((input_str = sql_value_text(arg3)) == NULL ||
-     (trim_set = sql_value_text(arg2)) == NULL)
+ if ((input_str = mem_as_ustr(arg3)) == NULL ||
+     (trim_set = mem_as_ustr(arg2)) == NULL)
    return;
 
  int trim_set_sz = sql_value_bytes(arg2);
@@ -1587,7 +1592,7 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
    context->is_aborted = true;
    return;
  }
- zIn = (u8 *) sql_value_text(argv[0]);
+ zIn = (u8 *) mem_as_ustr(argv[0]);
  if (zIn == 0)
    zIn = (u8 *) "";
  for (i = 0; zIn[i] && !sqlIsalpha(zIn[i]); i++) {
@@ -1836,7 +1841,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
    pAccum->mxAlloc = db->aLimit[SQL_LIMIT_LENGTH];
    if (!firstTerm) {
      if (argc == 2) {
-       zSep = (char *)sql_value_text(argv[1]);
+       zSep = mem_as_str0(argv[1]);
        nSep = sql_value_bytes(argv[1]);
      } else {
        zSep = ",";
@@ -1845,7 +1850,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
      if (zSep)
        sqlStrAccumAppend(pAccum, zSep, nSep);
    }
-   zVal = (char *)sql_value_text(argv[0]);
+   zVal = mem_as_str0(argv[0]);
    nVal = sql_value_bytes(argv[0]);
    if (zVal)
      sqlStrAccumAppend(pAccum, zVal, nVal);
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 6f787f7cc..8c2d09168 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1323,6 +1323,15 @@ mem_get_bool(const struct Mem *mem, bool *b)
  return -1;
 }
 
+int
+mem_get_str0(const struct Mem *mem, const char **s)
+{
+ if ((mem->flags & MEM_Str) == 0 || (mem->flags & MEM_Term) == 0)
+   return -1;
+ *s = mem->z;
+ return 0;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -2001,45 +2010,6 @@ mem_has_msgpack_subtype(struct Mem *mem)
         mem->subtype == SQL_SUBTYPE_MSGPACK;
 }
 
-/*
- * The pVal argument is known to be a value other than NULL.
- * Convert it into a string with encoding enc and return a pointer
- * to a zero-terminated version of that string.
- */
-static SQL_NOINLINE const void *
-valueToText(sql_value * pVal)
-{
- assert(pVal != 0);
- assert((pVal->flags & (MEM_Null)) == 0);
- if ((pVal->flags & (MEM_Blob | MEM_Str)) &&
-     !mem_has_msgpack_subtype(pVal)) {
-   if (ExpandBlob(pVal))
-     return 0;
-   pVal->flags |= MEM_Str;
-   sqlVdbeMemNulTerminate(pVal); /* IMP: R-31275-44060 */
- } else {
-   mem_to_str(pVal);
-   assert(0 == (1 & SQL_PTR_TO_INT(pVal->z)));
- }
- return pVal->z;
-}
-
-/*
- * It is already known that pMem contains an unterminated string.
- * Add the zero terminator.
- */
-static SQL_NOINLINE int
-vdbeMemAddTerminator(Mem * pMem)
-{
- if (sqlVdbeMemGrow(pMem, pMem->n + 2, 1)) {
-   return -1;
- }
- pMem->z[pMem->n] = 0;
- pMem->z[pMem->n + 1] = 0;
- pMem->flags |= MEM_Term;
- return 0;
-}
-
 /*
  * Both *pMem1 and *pMem2 contain string values. Compare the two values
  * using the collation sequence pColl. As usual, return a negative , zero
@@ -2141,7 +2111,9 @@ sql_value_type(sql_value *pVal)
 static SQL_NOINLINE int
 valueBytes(sql_value * pVal)
 {
- return valueToText(pVal) != 0 ? pVal->n : 0;
+ if (mem_to_str(pVal) != 0)
+   return 0;
+ return pVal->n;
 }
 
 int
@@ -2330,21 +2302,6 @@ registerTrace(int iReg, Mem *p) {
 }
 #endif
 
-/*
- * Make sure the given Mem is \u0000 terminated.
- */
-int
-sqlVdbeMemNulTerminate(Mem * pMem)
-{
- testcase((pMem->flags & (MEM_Term | MEM_Str)) == (MEM_Term | MEM_Str));
- testcase((pMem->flags & (MEM_Term | MEM_Str)) == 0);
- if ((pMem->flags & (MEM_Term | MEM_Str)) != MEM_Str) {
-   return 0; /* Nothing to do */
- } else {
-   return vdbeMemAddTerminator(pMem);
- }
-}
-
 /*
  * If the given Mem* has a zero-filled tail, turn it into an ordinary
  * blob stored in dynamically allocated space.
@@ -2503,7 +2460,9 @@ sql_value_blob(sql_value * pVal)
    p->flags |= MEM_Blob;
    return p->n ? p->z : 0;
  } else {
-   return sql_value_text(pVal);
+   if (mem_to_str(pVal) != 0)
+     return NULL;
+   return pVal->z;
  }
 }
 
@@ -2513,36 +2472,6 @@ sql_value_bytes(sql_value * pVal)
  return sqlValueBytes(pVal);
 }
 
-const unsigned char *
-sql_value_text(sql_value * pVal)
-{
- return (const unsigned char *)sqlValueText(pVal);
-}
-
-/* This function is only available internally, it is not part of the
- * external API. It works in a similar way to sql_value_text(),
- * except the data returned is in the encoding specified by the second
- * parameter, which must be one of SQL_UTF16BE, SQL_UTF16LE or
- * SQL_UTF8.
- *
- * (2006-02-16:)  The enc value can be or-ed with SQL_UTF16_ALIGNED.
- * If that is the case, then the result must be aligned on an even byte
- * boundary.
- */
-const void *
-sqlValueText(sql_value * pVal)
-{
- if (!pVal)
-   return 0;
- if ((pVal->flags & (MEM_Str | MEM_Term)) == (MEM_Str | MEM_Term)) {
-   return pVal->z;
- }
- if (pVal->flags & MEM_Null) {
-   return 0;
- }
- return valueToText(pVal);
-}
-
 /*
  * Return a pointer to static memory containing an SQL NULL value.
  */
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index e48e8788c..5848ae729 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -568,6 +568,28 @@ mem_get_double(const struct Mem *mem, double *d);
 int
 mem_get_bool(const struct Mem *mem, bool *b);
 
+/**
+ * Return value for MEM of STRING type if MEM contains a NULL-terminated string.
+ * Otherwise convert value of the MEM to NULL-terminated string if possible and
+ * return converted value. Original MEM is not changed.
+ */
+int
+mem_get_str0(const struct Mem *mem, const char **s);
+
+/**
+ * Return value for MEM of STRING type if MEM contains NULL-terminated string.
+ * Otherwise convert MEM to MEM of string type that contains NULL-terminated
+ * string and return its value. Return NULL if conversion is impossible.
+ */
+static inline const char *
+mem_as_str0(struct Mem *mem)
+{
+ const char *str;
+ if (mem_to_str0(mem) != 0 || mem_get_str0(mem, &str) != 0)
+   return NULL;
+ return str;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -601,7 +623,6 @@ registerTrace(int iReg, Mem *p);
 #define memIsValid(M) !mem_is_invalid(M)
 #endif
 
-int sqlVdbeMemNulTerminate(struct Mem *);
 int sqlVdbeMemExpandBlob(struct Mem *);
 #define ExpandBlob(P) (mem_is_zerobin(P)? sqlVdbeMemExpandBlob(P) : 0)
 
@@ -626,11 +647,6 @@ sql_value_blob(struct Mem *);
 int
 sql_value_bytes(struct Mem *);
 
-const unsigned char *
-sql_value_text(struct Mem *);
-
-const void *sqlValueText(struct Mem *);
-
 #define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))])
 
 const Mem *
diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
index 605478820..c3e5bb2e1 100644
--- a/src/box/sql/printf.c
+++ b/src/box/sql/printf.c
@@ -165,7 +165,8 @@ getTextArg(PrintfArguments * p)
 {
  if (p->nArg <= p->nUsed)
    return 0;
- return (char *)sql_value_text(p->apArg[p->nUsed++]);
+ struct Mem *mem = p->apArg[p->nUsed++];
+ return (char *)mem_as_str0(mem);
 }
 
 /*
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index df9469941..51613aea3 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -442,10 +442,6 @@ sql_column_bytes(sql_stmt *, int iCol);
 int
 sql_column_bytes16(sql_stmt *, int iCol);
 
-const unsigned char *
-sql_column_text(sql_stmt *,
-       int iCol);
-
 char *
 sql_result_to_msgpack(struct sql_stmt *stmt, uint32_t *tuple_size,
          struct region *region);
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 1d3c23a70..c229160b6 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -484,12 +484,6 @@ sql_column_bytes(sql_stmt * pStmt, int i)
  return sql_value_bytes(columnMem(pStmt, i));
 }
 
-const unsigned char *
-sql_column_text(sql_stmt * pStmt, int i)
-{
- return sql_value_text(columnMem(pStmt, i));
-}
-
 char *
 sql_result_to_msgpack(struct sql_stmt *stmt, uint32_t *tuple_size,
          struct region *region)
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index 93de722cb..fe7329ea8 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -312,8 +312,10 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
    pVal =
        sqlVdbeGetBoundValue(pReprepare, iCol,
               FIELD_TYPE_SCALAR);
-   if (pVal != NULL && mem_is_str(pVal))
-     z = (char *)sql_value_text(pVal);
+   if (pVal != NULL && mem_is_str(pVal)) {
+     if (mem_as_str0(pVal) == NULL)
+       return -1;
+   }
    assert(pRight->op == TK_VARIABLE || pRight->op == TK_REGISTER);
  } else if (op == TK_STRING) {
    z = pRight->u.zToken;

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

* [Tarantool-patches] [PATCH v5 50/52] sql: introduce mem_get_bin()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
                   ` (8 preceding siblings ...)
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 49/52] sql: introduce mem_get_str0() and mem_as_str0() Mergen Imeev via Tarantool-patches
@ 2021-04-09 21:08 ` Mergen Imeev via Tarantool-patches
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 51/52] sql: introduce mem_get_bytes_len() Mergen Imeev via Tarantool-patches
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 52/52] sql: introduce mem_get_agg() Mergen Imeev via Tarantool-patches
  11 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 21:08 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

Thank you for the review! My answer and new patch below.


On 30.03.2021 02:09, Vladislav Shpilevoy wrote:
> Thanks for the patch!
>
>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index 78f4ec3b5..199f3abef 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -72,6 +72,19 @@ mem_get_ustr(struct Mem *mem)
>>  	return (const unsigned char *)str;
>>  }
>>  
>> +static const void *
>> +mem_get_blob(struct Mem *mem)
>
> The same comments as in the previous patch with strings. Also you
> use 'binary' term in the other places, not 'blob'. I would propose to
> keep it consistent if we want to switch to 'binary' everywhere.
>
Thanks! Fixed.

>> +{
>> +	const char *s;
>> +	if (!mem_is_varstring(mem) && mem_convert_to_string(mem) != 0)
>> +		return NULL;
>> +	if (ExpandBlob(mem) != 0)
>> +		return NULL;
>> +	if (mem_get_binary(mem, &s) != 0)
>> +		return NULL;
>> +	return (const void *)s;
>> +}


New patch:

commit a3e0cc2e89663b816c476578dce24d05da6fb66b
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Mon Mar 22 11:54:01 2021 +0300

    sql: introduce mem_get_bin()
    
    This patch introduces mem_get_bin(). This function is used to receive
    binary value from MEM. If value of MEM is not binary value, it is
    converted to binary value if possible. MEM is not changed.
    
    Part of #5818

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index c1db6f78a..2896a5c31 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -54,6 +54,17 @@ mem_as_ustr(struct Mem *mem)
 	return (const unsigned char *)mem_as_str0(mem);
 }
 
+static const void *
+mem_as_bin(struct Mem *mem)
+{
+	const char *s;
+	if (!mem_is_bytes(mem) && mem_to_str(mem) != 0)
+		return NULL;
+	if (mem_get_bin(mem, &s) != 0)
+		return NULL;
+	return s;
+}
+
 /*
  * Return the collating function associated with a function.
  */
@@ -306,8 +317,8 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
 		const unsigned char *haystack_str;
 		const unsigned char *needle_str;
 		if (haystack_type == MP_BIN) {
-			needle_str = sql_value_blob(needle);
-			haystack_str = sql_value_blob(haystack);
+			needle_str = mem_as_bin(needle);
+			haystack_str = mem_as_bin(haystack);
 			assert(needle_str != NULL);
 			assert(haystack_str != NULL || n_haystack_bytes == 0);
 			/*
@@ -447,7 +458,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	mem_get_int(argv[1], &p1, &unused);
 	if (p0type == MP_BIN) {
 		len = sql_value_bytes(argv[0]);
-		z = sql_value_blob(argv[0]);
+		z = mem_as_bin(argv[0]);
 		if (z == 0)
 			return;
 		assert(len == sql_value_bytes(argv[0]));
@@ -1082,9 +1093,9 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_ARRAY:
 	case MP_MAP: {
 			char *zText = 0;
-			char const *zBlob = sql_value_blob(argv[0]);
+			char const *zBlob = mem_as_bin(argv[0]);
 			int nBlob = sql_value_bytes(argv[0]);
-			assert(zBlob == sql_value_blob(argv[0]));	/* No encoding change */
+			assert(zBlob == mem_as_bin(argv[0]));	/* No encoding change */
 			zText =
 			    (char *)contextMalloc(context,
 						  (2 * (i64) nBlob) + 4);
@@ -1218,9 +1229,9 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
 	char *zHex, *z;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	pBlob = sql_value_blob(argv[0]);
+	pBlob = mem_as_bin(argv[0]);
 	n = sql_value_bytes(argv[0]);
-	assert(pBlob == sql_value_blob(argv[0]));	/* No encoding change */
+	assert(pBlob == mem_as_bin(argv[0]));	/* No encoding change */
 	z = zHex = contextMalloc(context, ((i64) n) * 2 + 1);
 	if (zHex) {
 		for (i = 0; i < n; i++, pBlob++) {
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 8c2d09168..2e69f4a80 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1332,6 +1332,19 @@ mem_get_str0(const struct Mem *mem, const char **s)
 	return 0;
 }
 
+int
+mem_get_bin(const struct Mem *mem, const char **s)
+{
+	if ((mem->flags & MEM_Str) != 0) {
+		*s = mem->n > 0 ? mem->z : NULL;
+		return 0;
+	}
+	if ((mem->flags & MEM_Blob) == 0 || (mem->flags & MEM_Zero) != 0)
+		return -1;
+	*s = mem->z;
+	return 0;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -2444,28 +2457,6 @@ releaseMemArray(Mem * p, int N)
 	}
 }
 
-/**************************** sql_value_  ******************************
- * The following routines extract information from a Mem or sql_value
- * structure.
- */
-const void *
-sql_value_blob(sql_value * pVal)
-{
-	Mem *p = (Mem *) pVal;
-	if (p->flags & (MEM_Blob | MEM_Str)) {
-		if (ExpandBlob(p) != 0) {
-			assert(p->flags == MEM_Null && p->z == 0);
-			return 0;
-		}
-		p->flags |= MEM_Blob;
-		return p->n ? p->z : 0;
-	} else {
-		if (mem_to_str(pVal) != 0)
-			return NULL;
-		return pVal->z;
-	}
-}
-
 int
 sql_value_bytes(sql_value * pVal)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 5848ae729..e2d0f343e 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -590,6 +590,14 @@ mem_as_str0(struct Mem *mem)
 	return str;
 }
 
+/**
+ * Return value for MEM of VARBINARY type. For MEM of all other types convert
+ * value of the MEM to VARBINARY if possible and return converted value.
+ * Original MEM is not changed.
+ */
+int
+mem_get_bin(const struct Mem *mem, const char **s);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -641,9 +649,6 @@ releaseMemArray(Mem * p, int N);
 
 /** Getters. */
 
-const void *
-sql_value_blob(struct Mem *);
-
 int
 sql_value_bytes(struct Mem *);
 
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 51613aea3..8a7a87b49 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -433,9 +433,6 @@ sql_stmt_compile(const char *sql, int bytes_count, struct Vdbe *re_prepared,
 int
 sql_step(sql_stmt *);
 
-const void *
-sql_column_blob(sql_stmt *, int iCol);
-
 int
 sql_column_bytes(sql_stmt *, int iCol);
 
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index c229160b6..19b70a55b 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -470,13 +470,6 @@ columnMem(sql_stmt * pStmt, int i)
  * The following routines are used to access elements of the current row
  * in the result set.
  */
-const void *
-sql_column_blob(sql_stmt * pStmt, int i)
-{
-	const void *val;
-	val = sql_value_blob(columnMem(pStmt, i));
-	return val;
-}
 
 int
 sql_column_bytes(sql_stmt * pStmt, int i)

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

* [Tarantool-patches] [PATCH v5 51/52] sql: introduce mem_get_bytes_len()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
                   ` (9 preceding siblings ...)
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 50/52] sql: introduce mem_get_bin() Mergen Imeev via Tarantool-patches
@ 2021-04-09 21:08 ` Mergen Imeev via Tarantool-patches
  2021-04-13 23:06   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 52/52] sql: introduce mem_get_agg() Mergen Imeev via Tarantool-patches
  11 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 21:08 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

Thank you for the review! My answer and new patch below. Also, I see that I
did a lot of misspelling and other errors in my answers and I am very sorry
about this. I will try to add more info to my answers tomorrow.


On 30.03.2021 02:09, Vladislav Shpilevoy wrote:
> Thanks for the patch!
>
>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index 199f3abef..6687fca3c 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -967,7 +968,7 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
>>  		return;                                                        \
>>  	}                                                                      \
>>  	z2 = mem_get_str(argv[0]);                                            \
>> -	n = sql_value_bytes(argv[0]);                                      \
>> +	n = mem_get_length(argv[0]);                                      \
>
> All these \ were aligned before your patchset. Please, keep it straight.
>
Fixed.

>>  	/*                                                                     \
>>  	 * Verify that the call to _bytes()                                    \
>>  	 * does not invalidate the _text() pointer.                            \


New patch:

commit 326fac6c97ae260b32be9987e48e3381c600ba9c
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Mon Mar 22 12:27:33 2021 +0300

    sql: introduce mem_get_bytes_len()
    
    This patch introduces mem_get_bytes_len(). This function is used to
    receive length of string or binary value of MEM. If MEM is not
    of STRING or VARBINARy type this function returns -1.
    
    Part of #5818

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 2896a5c31..746bda0f4 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -65,6 +65,14 @@ mem_as_bin(struct Mem *mem)
 	return s;
 }
 
+static int
+mem_get_length(struct Mem *mem)
+{
+	uint32_t len;
+	mem_get_bytes_len(mem, &len);
+	return len;
+}
+
 /*
  * Return the collating function associated with a function.
  */
@@ -187,14 +195,15 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_UINT:
 	case MP_BOOL:
 	case MP_DOUBLE:{
-			sql_result_uint(context, sql_value_bytes(argv[0]));
+			mem_as_bin(argv[0]);
+			sql_result_uint(context, mem_get_length(argv[0]));
 			break;
 		}
 	case MP_STR:{
 			const unsigned char *z = mem_as_ustr(argv[0]);
 			if (z == 0)
 				return;
-			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
+			len = sql_utf8_char_count(z, mem_get_length(argv[0]));
 			sql_result_uint(context, len);
 			break;
 		}
@@ -310,8 +319,8 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
 		return;
 	}
 
-	int n_needle_bytes = sql_value_bytes(needle);
-	int n_haystack_bytes = sql_value_bytes(haystack);
+	int n_needle_bytes = mem_get_length(needle);
+	int n_haystack_bytes = mem_get_length(haystack);
 	int position = 1;
 	if (n_needle_bytes > 0) {
 		const unsigned char *haystack_str;
@@ -457,18 +466,18 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	bool unused;
 	mem_get_int(argv[1], &p1, &unused);
 	if (p0type == MP_BIN) {
-		len = sql_value_bytes(argv[0]);
 		z = mem_as_bin(argv[0]);
+		len = mem_get_length(argv[0]);
 		if (z == 0)
 			return;
-		assert(len == sql_value_bytes(argv[0]));
+		assert(len == mem_get_length(argv[0]));
 	} else {
 		z = mem_as_ustr(argv[0]);
 		if (z == 0)
 			return;
 		len = 0;
 		if (p1 < 0)
-			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
+			len = sql_utf8_char_count(z, mem_get_length(argv[0]));
 	}
 	if (argc == 3) {
 		mem_get_int(argv[2], &p2, &unused);
@@ -507,7 +516,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 		 * used because '\0' is not supposed to be
 		 * end-of-string symbol.
 		 */
-		int byte_size = sql_value_bytes(argv[0]);
+		int byte_size = mem_get_length(argv[0]);
 		int n_chars = sql_utf8_char_count(z, byte_size);
 		int cnt = 0;
 		int i = 0;
@@ -627,7 +636,7 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
 		return;                                                        \
 	}                                                                      \
 	z2 = mem_as_str0(argv[0]);                                             \
-	n = sql_value_bytes(argv[0]);                                      \
+	n = mem_get_length(argv[0]);                                           \
 	/*                                                                     \
 	 * Verify that the call to _bytes()                                    \
 	 * does not invalidate the _text() pointer.                            \
@@ -965,15 +974,15 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 	}
 	const char *zB = mem_as_str0(argv[0]);
 	const char *zA = mem_as_str0(argv[1]);
-	const char *zB_end = zB + sql_value_bytes(argv[0]);
-	const char *zA_end = zA + sql_value_bytes(argv[1]);
+	const char *zB_end = zB + mem_get_length(argv[0]);
+	const char *zA_end = zA + mem_get_length(argv[1]);
 
 	/*
 	 * Limit the length of the LIKE pattern to avoid problems
 	 * of deep recursion and N*N behavior in
 	 * sql_utf8_pattern_compare().
 	 */
-	nPat = sql_value_bytes(argv[0]);
+	nPat = mem_get_length(argv[0]);
 	testcase(nPat == db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH]);
 	testcase(nPat == db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH] + 1);
 	if (nPat > db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH]) {
@@ -994,7 +1003,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		const unsigned char *zEsc = mem_as_ustr(argv[2]);
 		if (zEsc == 0)
 			return;
-		if (sql_utf8_char_count(zEsc, sql_value_bytes(argv[2])) != 1) {
+		if (sql_utf8_char_count(zEsc, mem_get_length(argv[2])) != 1) {
 			diag_set(ClientError, ER_SQL_EXECUTE, "ESCAPE "\
 				 "expression must be a single character");
 			context->is_aborted = true;
@@ -1094,7 +1103,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_MAP: {
 			char *zText = 0;
 			char const *zBlob = mem_as_bin(argv[0]);
-			int nBlob = sql_value_bytes(argv[0]);
+			int nBlob = mem_get_length(argv[0]);
 			assert(zBlob == mem_as_bin(argv[0]));	/* No encoding change */
 			zText =
 			    (char *)contextMalloc(context,
@@ -1230,7 +1239,7 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
 	pBlob = mem_as_bin(argv[0]);
-	n = sql_value_bytes(argv[0]);
+	n = mem_get_length(argv[0]);
 	assert(pBlob == mem_as_bin(argv[0]));	/* No encoding change */
 	z = zHex = contextMalloc(context, ((i64) n) * 2 + 1);
 	if (zHex) {
@@ -1289,7 +1298,7 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 	zStr = mem_as_ustr(argv[0]);
 	if (zStr == 0)
 		return;
-	nStr = sql_value_bytes(argv[0]);
+	nStr = mem_get_length(argv[0]);
 	assert(zStr == mem_as_ustr(argv[0]));	/* No encoding change */
 	zPattern = mem_as_ustr(argv[1]);
 	if (zPattern == 0) {
@@ -1297,7 +1306,7 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 		       || sql_context_db_handle(context)->mallocFailed);
 		return;
 	}
-	nPattern = sql_value_bytes(argv[1]);
+	nPattern = mem_get_length(argv[1]);
 	if (nPattern == 0) {
 		assert(!mem_is_null(argv[1]));
 		sql_result_value(context, argv[0]);
@@ -1307,7 +1316,7 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 	zRep = mem_as_ustr(argv[2]);
 	if (zRep == 0)
 		return;
-	nRep = sql_value_bytes(argv[2]);
+	nRep = mem_get_length(argv[2]);
 	assert(zRep == mem_as_ustr(argv[2]));
 	nOut = nStr + 1;
 	assert(nOut < SQL_MAX_LENGTH);
@@ -1469,7 +1478,7 @@ trim_func_one_arg(struct sql_context *context, sql_value *arg)
 		default_trim = (const unsigned char *) "\0";
 	else
 		default_trim = (const unsigned char *) " ";
-	int input_str_sz = sql_value_bytes(arg);
+	int input_str_sz = mem_get_length(arg);
 	const unsigned char *input_str = mem_as_ustr(arg);
 	uint8_t trim_char_len[1] = { 1 };
 	trim_procedure(context, TRIM_BOTH, default_trim, trim_char_len, 1,
@@ -1495,7 +1504,7 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
 	if ((input_str = mem_as_ustr(arg2)) == NULL)
 		return;
 
-	int input_str_sz = sql_value_bytes(arg2);
+	int input_str_sz = mem_get_length(arg2);
 	if (sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT) {
 		uint8_t len_one = 1;
 		uint64_t n;
@@ -1503,7 +1512,7 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
 		trim_procedure(context, n, (const unsigned char *) " ",
 			       &len_one, 1, input_str, input_str_sz);
 	} else if ((trim_set = mem_as_ustr(arg1)) != NULL) {
-		int trim_set_sz = sql_value_bytes(arg1);
+		int trim_set_sz = mem_get_length(arg1);
 		uint8_t *char_len;
 		int char_cnt = trim_prepare_char_len(context, trim_set,
 						     trim_set_sz, &char_len);
@@ -1532,8 +1541,8 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
 	    (trim_set = mem_as_ustr(arg2)) == NULL)
 		return;
 
-	int trim_set_sz = sql_value_bytes(arg2);
-	int input_str_sz = sql_value_bytes(arg3);
+	int trim_set_sz = mem_get_length(arg2);
+	int input_str_sz = mem_get_length(arg3);
 	uint8_t *char_len;
 	int char_cnt = trim_prepare_char_len(context, trim_set, trim_set_sz,
 					     &char_len);
@@ -1853,7 +1862,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 		if (!firstTerm) {
 			if (argc == 2) {
 				zSep = mem_as_str0(argv[1]);
-				nSep = sql_value_bytes(argv[1]);
+				nSep = mem_get_length(argv[1]);
 			} else {
 				zSep = ",";
 				nSep = 1;
@@ -1862,7 +1871,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 				sqlStrAccumAppend(pAccum, zSep, nSep);
 		}
 		zVal = mem_as_str0(argv[0]);
-		nVal = sql_value_bytes(argv[0]);
+		nVal = mem_get_length(argv[0]);
 		if (zVal)
 			sqlStrAccumAppend(pAccum, zVal, nVal);
 	}
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 2e69f4a80..3e9544cae 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1345,6 +1345,18 @@ mem_get_bin(const struct Mem *mem, const char **s)
 	return 0;
 }
 
+int
+mem_get_bytes_len(const struct Mem *mem, uint32_t *len)
+{
+	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
+		return -1;
+	if ((mem->flags & MEM_Blob) !=0 && (mem->flags & MEM_Zero) != 0)
+		*len = mem->n + mem->u.nZero;
+	else
+		*len = mem->n;
+	return 0;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -2116,41 +2128,6 @@ sql_value_type(sql_value *pVal)
 	return mem_mp_type(mem);
 }
 
-/*
- * The sqlValueBytes() routine returns the number of bytes in the
- * sql_value object assuming that it uses the encoding "enc".
- * The valueBytes() routine is a helper function.
- */
-static SQL_NOINLINE int
-valueBytes(sql_value * pVal)
-{
-	if (mem_to_str(pVal) != 0)
-		return 0;
-	return pVal->n;
-}
-
-int
-sqlValueBytes(sql_value * pVal)
-{
-	Mem *p = (Mem *) pVal;
-	assert((p->flags & MEM_Null) == 0
-	       || (p->flags & (MEM_Str | MEM_Blob)) == 0);
-	if ((p->flags & MEM_Str) != 0) {
-		return p->n;
-	}
-	if ((p->flags & MEM_Blob) != 0) {
-		if (p->flags & MEM_Zero) {
-			return p->n + p->u.nZero;
-		} else {
-			return p->n;
-		}
-	}
-	if (p->flags & MEM_Null)
-		return 0;
-	return valueBytes(pVal);
-}
-
-
 #ifdef SQL_DEBUG
 /*
  * Check invariants on a Mem object.
@@ -2457,53 +2434,6 @@ releaseMemArray(Mem * p, int N)
 	}
 }
 
-int
-sql_value_bytes(sql_value * pVal)
-{
-	return sqlValueBytes(pVal);
-}
-
-/*
- * Return a pointer to static memory containing an SQL NULL value.
- */
-const Mem *
-columnNullValue(void)
-{
-	/* Even though the Mem structure contains an element
-	 * of type i64, on certain architectures (x86) with certain compiler
-	 * switches (-Os), gcc may align this Mem object on a 4-byte boundary
-	 * instead of an 8-byte one. This all works fine, except that when
-	 * running with SQL_DEBUG defined the sql code sometimes assert()s
-	 * that a Mem structure is located on an 8-byte boundary. To prevent
-	 * these assert()s from failing, when building with SQL_DEBUG defined
-	 * using gcc, we force nullMem to be 8-byte aligned using the magical
-	 * __attribute__((aligned(8))) macro.
-	 */
-	static const Mem nullMem
-#if defined(SQL_DEBUG) && defined(__GNUC__)
-	    __attribute__ ((aligned(8)))
-#endif
-	    = {
-		/* .u          = */  {
-		0},
-		    /* .flags      = */ (u16) MEM_Null,
-		    /* .eSubtype   = */ (u8) 0,
-		    /* .field_type = */ field_type_MAX,
-		    /* .n          = */ (int)0,
-		    /* .z          = */ (char *)0,
-		    /* .zMalloc    = */ (char *)0,
-		    /* .szMalloc   = */ (int)0,
-		    /* .uTemp      = */ (u32) 0,
-		    /* .db         = */ (sql *) 0,
-		    /* .xDel       = */ (void (*)(void *))0,
-#ifdef SQL_DEBUG
-		    /* .pScopyFrom = */ (Mem *) 0,
-		    /* .pFiller    = */ (void *)0,
-#endif
-	};
-	return &nullMem;
-}
-
 /*
  * Return true if the Mem object contains a TEXT or BLOB that is
  * too large - whose size exceeds SQL_MAX_LENGTH.
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index e2d0f343e..9c6d18e1f 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -598,6 +598,13 @@ mem_as_str0(struct Mem *mem)
 int
 mem_get_bin(const struct Mem *mem, const char **s);
 
+/**
+ * Return length of value for MEM of STRING or VARBINARY type. Original MEM is
+ * not changed.
+ */
+int
+mem_get_bytes_len(const struct Mem *mem, uint32_t *len);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -616,8 +623,6 @@ mem_mp_type(struct Mem *mem);
 enum mp_type
 sql_value_type(struct Mem *);
 
-int sqlValueBytes(struct Mem *);
-
 #ifdef SQL_DEBUG
 int sqlVdbeCheckMemInvariants(struct Mem *);
 void sqlVdbeMemPrettyPrint(Mem * pMem, char *zBuf);
@@ -647,18 +652,8 @@ struct Mem *sqlValueNew(struct sql *);
 void
 releaseMemArray(Mem * p, int N);
 
-/** Getters. */
-
-int
-sql_value_bytes(struct Mem *);
-
 #define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))])
 
-const Mem *
-columnNullValue(void);
-
-/** Checkers. */
-
 int sqlVdbeMemTooBig(Mem *);
 
 int sqlMemCompare(const Mem *, const Mem *, const struct coll *);
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 8a7a87b49..8feb112f5 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -433,9 +433,6 @@ sql_stmt_compile(const char *sql, int bytes_count, struct Vdbe *re_prepared,
 int
 sql_step(sql_stmt *);
 
-int
-sql_column_bytes(sql_stmt *, int iCol);
-
 int
 sql_column_bytes16(sql_stmt *, int iCol);
 
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 19b70a55b..b0334f3ed 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -442,41 +442,6 @@ sql_data_count(sql_stmt * pStmt)
 	return pVm->nResColumn;
 }
 
-/*
- * Check to see if column iCol of the given statement is valid.  If
- * it is, return a pointer to the Mem for the value of that column.
- * If iCol is not valid, return a pointer to a Mem which has a value
- * of NULL.
- */
-static Mem *
-columnMem(sql_stmt * pStmt, int i)
-{
-	Vdbe *pVm;
-	Mem *pOut;
-
-	pVm = (Vdbe *) pStmt;
-	if (pVm == 0)
-		return (Mem *) columnNullValue();
-	assert(pVm->db);
-	if (pVm->pResultSet != 0 && i < pVm->nResColumn && i >= 0) {
-		pOut = &pVm->pResultSet[i];
-	} else {
-		pOut = (Mem *) columnNullValue();
-	}
-	return pOut;
-}
-
-/**************************** sql_column_  ******************************
- * The following routines are used to access elements of the current row
- * in the result set.
- */
-
-int
-sql_column_bytes(sql_stmt * pStmt, int i)
-{
-	return sql_value_bytes(columnMem(pStmt, i));
-}
-
 char *
 sql_result_to_msgpack(struct sql_stmt *stmt, uint32_t *tuple_size,
 		      struct region *region)

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

* [Tarantool-patches] [PATCH v5 52/52] sql: introduce mem_get_agg()
       [not found] <cover.1618000036.git.imeevma@gmail.com>
                   ` (10 preceding siblings ...)
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 51/52] sql: introduce mem_get_bytes_len() Mergen Imeev via Tarantool-patches
@ 2021-04-09 21:08 ` Mergen Imeev via Tarantool-patches
  11 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-09 21:08 UTC (permalink / raw)
  To: v.shpilevoy, tsafin; +Cc: tarantool-patches

This patch introduces mem_get_agg(). This function is used to receive
address of memory allocated for accumulation structure of the aggregate
function.

Closes #5818
---
 src/box/sql/mem.c     | 9 +++++++++
 src/box/sql/mem.h     | 7 +++++++
 src/box/sql/vdbeapi.c | 5 ++++-
 3 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 3e9544cae..c7220190c 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1357,6 +1357,15 @@ mem_get_bytes_len(const struct Mem *mem, uint32_t *len)
 	return 0;
 }
 
+int
+mem_get_agg(const struct Mem *mem, void **accum)
+{
+	if ((mem->flags & MEM_Agg) == 0)
+		return -1;
+	*accum = mem->z;
+	return 0;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 9c6d18e1f..9ba7ef066 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -605,6 +605,13 @@ mem_get_bin(const struct Mem *mem, const char **s);
 int
 mem_get_bytes_len(const struct Mem *mem, uint32_t *len);
 
+/**
+ * Return address of memory allocated for accumulation structure of the
+ * aggregate function.
+ */
+int
+mem_get_agg(const struct Mem *mem, void **accum);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index b0334f3ed..a9a7dc97a 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -416,7 +416,10 @@ sql_aggregate_context(sql_context * p, int nByte)
 	assert(p->func->def->aggregate == FUNC_AGGREGATE_GROUP);
 	if (!mem_is_agg(p->pMem) && mem_set_agg(p->pMem, p->func, nByte) != 0)
 		return NULL;
-	return p->pMem->z;
+	void *accum;
+	if (mem_get_agg(p->pMem, &accum) != 0)
+		return NULL;
+	return accum;
 }
 
 /*
-- 
2.25.1


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

* Re: [Tarantool-patches] [PATCH v5 42/52] sql: introduce mem_to_str() and mem_to_str0()
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 42/52] sql: introduce mem_to_str() and mem_to_str0() Mergen Imeev via Tarantool-patches
@ 2021-04-13 22:58   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-13 23:41     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-13 22:58 UTC (permalink / raw)
  To: imeevma, tsafin; +Cc: tarantool-patches

Thanks for working on this!

> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index 9a0234e60..be7b47e76 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -822,6 +822,130 @@ mem_to_number(struct Mem *mem)

<...>

> +
> +int
> +mem_to_str0(struct Mem *mem)
> +{
> +	if ((mem->flags & (MEM_Str | MEM_Term)) == (MEM_Str | MEM_Term))
> +		return 0;
> +	if ((mem->flags & MEM_Str) != 0)
> +		return str_to_str0(mem);
> +	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +		return int_to_str0(mem);
> +	if ((mem->flags & MEM_Real) != 0)
> +		return double_to_str0(mem);
> +	if ((mem->flags & MEM_Bool) != 0)
> +		return bool_to_str0(mem);
> +	if ((mem->flags & MEM_Blob) != 0) {
> +		if ((mem->flags & MEM_Subtype) == 0)
> +			return bin_to_str0(mem);
> +		if (mp_typeof(*mem->z) == MP_MAP)
> +			return map_to_str0(mem);
> +		return array_to_str0(mem);
> +	}
> +	return -1;
> +}
> +
> +int
> +mem_to_str(struct Mem *mem)
> +{
> +	if ((mem->flags & MEM_Str) != 0)
> +		return 0;
> +	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +		return int_to_str0(mem);
> +	if ((mem->flags & MEM_Real) != 0)
> +		return double_to_str0(mem);
> +	if ((mem->flags & MEM_Bool) != 0)
> +		return bool_to_str0(mem);
> +	if ((mem->flags & MEM_Blob) != 0) {
> +		if ((mem->flags & MEM_Subtype) == 0)
> +			return bin_to_str(mem);
> +		if (mp_typeof(*mem->z) == MP_MAP)
> +			return map_to_str0(mem);
> +		return array_to_str0(mem);
> +	}
> +	return -1;

In the old function there was an assertion that only simple types
are passed (no Aggs, Frames). Please, keep it, or add a diag_set()
here. The same above.

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

* Re: [Tarantool-patches] [PATCH v5 43/52] sql: introduce mem_cast_explicit()
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 43/52] sql: introduce mem_cast_explicit() Mergen Imeev via Tarantool-patches
@ 2021-04-13 22:59   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-14  0:01     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-13 22:59 UTC (permalink / raw)
  To: imeevma, tsafin; +Cc: tarantool-patches

Thanks for the patch!

See 2 comments below.

> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index be7b47e76..45d2d5fe3 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -946,6 +946,131 @@ mem_to_str(struct Mem *mem)
>  	return -1;
>  }
>  
> +static inline int
> +bytes_to_uint(struct Mem *mem)
> +{
> +	bool is_neg;
> +	int64_t i;
> +	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
> +		return -1;
> +	if (is_neg)
> +		return -1;
> +	mem_set_uint(mem, (uint64_t)i);
> +	return 0;
> +}
> +
> +static inline int
> +str_to_bool(struct Mem *mem)
> +{
> +	char *str = mem->z;
> +	bool b;
> +	const char *str_true = "TRUE";
> +	const char *str_false = "FALSE";
> +	uint32_t len_true = strlen(str_true);
> +	uint32_t len_false = strlen(str_false);
> +
> +	for (; str[0] == ' '; str++);
> +	if (strncasecmp(str, str_true, len_true) == 0) {
> +		b = true;
> +		str += len_true;
> +	} else if (strncasecmp(str, str_false, len_false) == 0) {
> +		b = false;
> +		str += len_false;
> +	} else {
> +		return -1;
> +	}
> +	for (; str[0] == ' '; str++);
> +	if (str[0] != '\0')
> +		return -1;
> +	mem_set_bool(mem, b);
> +	return 0;
> +}
> +
> +static inline int
> +int_to_bool(struct Mem *mem)
> +{
> +	mem->u.b = mem->u.i != 0;
> +	mem->flags = MEM_Bool;
> +	mem->field_type = FIELD_TYPE_BOOLEAN;
> +	return 0;
> +}
> +
> +static inline int
> +double_to_bool(struct Mem *mem)
> +{
> +	mem->u.b = mem->u.r != 0.;
> +	mem->flags = MEM_Bool;
> +	mem->field_type = FIELD_TYPE_BOOLEAN;
> +	return 0;
> +}
> +
> +static inline int
> +str_to_bin(struct Mem *mem)
> +{
> +	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
> +		     MEM_Blob;
> +	mem->field_type = FIELD_TYPE_VARBINARY;
> +	return 0;
> +}

1. You have tons of <src>_to_<dst> converters. I propose you to group them.
For example, str_to_* all together, double_to_* all together, and so on.
It would simplify reading and search.

> +
> +int
> +mem_cast_explicit(struct Mem *mem, enum field_type type)
> +{
> +	if ((mem->flags & MEM_Null) != 0) {
> +		mem->field_type = type;
> +		return 0;
> +	}
> +	switch (type) {
> +	case FIELD_TYPE_UNSIGNED:
> +		if ((mem->flags & MEM_UInt) != 0)
> +			return 0;
> +		if ((mem->flags & MEM_Int) != 0)
> +			return -1;
> +		if ((mem->flags & MEM_Blob) != 0 &&
> +		    (mem->flags & MEM_Subtype) != 0)
> +			return -1;
> +		if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
> +			return bytes_to_uint(mem);
> +		if ((mem->flags & MEM_Real) != 0)
> +			return double_to_int(mem);

2. tarantool> box.execute('SELECT CAST(-1.1 AS UNSIGNED);')
---
- metadata:
  - name: COLUMN_1
    type: unsigned
  rows:
  - [-1]
...

That looks quite broken. Is this a known issue? From the
code I see the issue existed before your patch (but I was
too lazy to try it).

> @@ -2018,113 +2107,6 @@ registerTrace(int iReg, Mem *p) {
>  }
>  #endif
>  
> -/*
> - * Cast the datatype of the value in pMem according to the type
> - * @type.  Casting is different from applying type in that a cast
> - * is forced.  In other words, the value is converted into the desired
> - * type even if that results in loss of data.  This routine is
> - * used (for example) to implement the SQL "cast()" operator.
> - */
> -int
> -sqlVdbeMemCast(Mem * pMem, enum field_type type)
> -{
> -	assert(type < field_type_MAX);
> -	if (pMem->flags & MEM_Null)
> -		return 0;
> -	switch (type) {
> -	case FIELD_TYPE_SCALAR:
> -		return 0;
> -	case FIELD_TYPE_BOOLEAN:
> -		if ((pMem->flags & MEM_Int) != 0) {
> -			mem_set_bool(pMem, pMem->u.i);
> -			return 0;
> -		}
> -		if ((pMem->flags & MEM_UInt) != 0) {
> -			mem_set_bool(pMem, pMem->u.u);
> -			return 0;
> -		}
> -		if ((pMem->flags & MEM_Real) != 0) {
> -			mem_set_bool(pMem, pMem->u.r);
> -			return 0;
> -		}
> -		if ((pMem->flags & MEM_Str) != 0) {
> -			bool value;
> -			if (str_cast_to_boolean(pMem->z, &value) != 0)
> -				return -1;
> -			mem_set_bool(pMem, value);
> -			return 0;
> -		}
> -		if ((pMem->flags & MEM_Bool) != 0)
> -			return 0;
> -		return -1;
> -	case FIELD_TYPE_INTEGER:
> -	case FIELD_TYPE_UNSIGNED:
> -		if ((pMem->flags & (MEM_Blob | MEM_Str)) != 0) {
> -			bool is_neg;
> -			int64_t val;
> -			if (sql_atoi64(pMem->z, &val, &is_neg, pMem->n) != 0)
> -				return -1;
> -			if (type == FIELD_TYPE_UNSIGNED && is_neg)
> -				return -1;
> -			mem_set_int(pMem, val, is_neg);
> -			return 0;
> -		}
> -		if ((pMem->flags & MEM_Bool) != 0) {
> -			pMem->u.u = (uint64_t)pMem->u.b;
> -			pMem->flags = MEM_UInt;
> -			pMem->field_type = FIELD_TYPE_UNSIGNED;
> -			return 0;
> -		}
> -		if ((pMem->flags & MEM_Real) != 0) {
> -			double d = pMem->u.r;
> -			if (d < 0. && d >= (double)INT64_MIN) {
> -				pMem->u.i = (int64_t)d;
> -				pMem->flags = MEM_Int;
> -				pMem->field_type = FIELD_TYPE_INTEGER;
> -				return 0;
> -			}
> -			if (d >= 0. && d < (double)UINT64_MAX) {
> -				pMem->u.u = (uint64_t)d;
> -				pMem->flags = MEM_UInt;
> -				pMem->field_type = FIELD_TYPE_UNSIGNED;
> -				return 0;
> -			}
> -			return -1;
> -		}
> -		if (type == FIELD_TYPE_UNSIGNED &&
> -		    (pMem->flags & MEM_UInt) == 0)
> -			return -1;
> -		return 0;
> -	case FIELD_TYPE_DOUBLE:
> -		return mem_to_double(pMem);
> -	case FIELD_TYPE_NUMBER:
> -		return mem_to_number(pMem);
> -	case FIELD_TYPE_VARBINARY:
> -		if ((pMem->flags & MEM_Blob) != 0)
> -			return 0;
> -		if ((pMem->flags & MEM_Str) != 0) {
> -			MemSetTypeFlag(pMem, MEM_Str);
> -			return 0;
> -		}
> -		return -1;
> -	default:
> -		assert(type == FIELD_TYPE_STRING);
> -		assert(MEM_Str == (MEM_Blob >> 3));
> -		if ((pMem->flags & MEM_Bool) != 0) {
> -			const char *str_bool = SQL_TOKEN_BOOLEAN(pMem->u.b);
> -			if (mem_copy_str0(pMem, str_bool) != 0)
> -				return -1;
> -			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);
> -		pMem->flags &=
> -			~(MEM_Int | MEM_UInt | MEM_Real | MEM_Blob | MEM_Zero);
> -		return 0;
> -	}
> -}

It is fascinating how a good code structure allows to get rid of
all of that old garbage mess almost naturally, and reveals some
issues.

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

* Re: [Tarantool-patches] [PATCH v5 44/52] sql: introduce mem_cast_implicit()
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 44/52] sql: introduce mem_cast_implicit() Mergen Imeev via Tarantool-patches
@ 2021-04-13 22:59   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-14  0:05     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-13 22:59 UTC (permalink / raw)
  To: imeevma, tsafin; +Cc: tarantool-patches

Good job on the fixes!

> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 29634841a..72a84e80d 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -2092,23 +2092,12 @@ case OP_ApplyType: {
>  	while((type = *(types++)) != field_type_MAX) {
>  		assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
>  		assert(memIsValid(pIn1));
> -		if (!mem_is_type_compatible(pIn1, type)) {
> -			/* Implicit cast is allowed only to numeric type. */
> -			if (!sql_type_is_numeric(type))
> -				goto type_mismatch;
> -			/* Implicit cast is allowed only from numeric type. */
> -			if (!mem_is_num(pIn1))
> -				goto type_mismatch;
> -			/* Try to convert numeric-to-numeric. */
> -			if (mem_cast_explicit(pIn1, type) != 0)
> -				goto type_mismatch;
> +		if (mem_cast_implicit(pIn1, type) != 0) {
> +			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
> +				 mem_str(pIn1), field_type_strs[type]);
> +			goto abort_due_to_error;

I think it would be better both for explicit and implicit casts
to have the diag_set() inside.

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

* Re: [Tarantool-patches] [PATCH v5 45/52] sql: introduce mem_get_int()
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 45/52] sql: introduce mem_get_int() Mergen Imeev via Tarantool-patches
@ 2021-04-13 23:01   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-14  0:28     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-13 23:01 UTC (permalink / raw)
  To: imeevma, tsafin; +Cc: tarantool-patches

Nice fixes!

On 09.04.2021 22:53, Mergen Imeev via Tarantool-patches wrote:
> Thank you for the review! My answers and new patch below.
> 
> 
> On 30.03.2021 02:08, Vladislav Shpilevoy wrote:
>> Thanks for the patch!
>>
>>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>>> index b644c39d8..0fa0f6ac7 100644
>>> --- a/src/box/sql/func.c
>>> +++ b/src/box/sql/func.c
>>> @@ -1532,10 +1543,11 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
>>>  static void
>>>  zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
>>>  {
>>> -	i64 n;
>>> +	int64_t n;
>>>  	assert(argc == 1);
>>>  	UNUSED_PARAMETER(argc);
>>> -	n = sql_value_int64(argv[0]);
>>> +	bool unused;
>>> +	mem_get_integer(argv[0], &n, &unused);
>>
>> The flag is never used anywhere except one assertion where you can
>> check the integer value instead. I think you can drop this out
>> parameter. In future we could add mem_get_int_with_sign() or something
>> like that if necessary.
> I think the problem here mostly because most of built-in functions and bitwise
> operations cannot work with our INTEGER. They can only work with int64. I
> believe, if we fix this problem, there will be no problems with having this
> flag.

My complaint is about the flag. The third argument which is almost never
used. It makes the code ugly, and does not give a clue it is broken in fact.
When uint64_t is > INT64_MAX and is returned as int64_t and the flag is
ignored.

What about mem_get_int_unsafe()? It would return int64_t truncated like
before. Return as 'return', not out parameter. Because we also never check
for fail as I see. And no 'unused' flag. But we would clearly see that these
places are broken and need attention.

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

* Re: [Tarantool-patches] [PATCH v5 46/52] sql: introduce mem_get_uint()
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 46/52] sql: introduce mem_get_uint() Mergen Imeev via Tarantool-patches
@ 2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-14  0:39     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-13 23:04 UTC (permalink / raw)
  To: imeevma, tsafin; +Cc: tarantool-patches

Thanks for the fixes!

On 09.04.2021 23:08, Mergen Imeev via Tarantool-patches wrote:
> Thank you for the review! My answer and new patch below.
> 
> 
> On 30.03.2021 02:08, Vladislav Shpilevoy wrote:
>> Thanks for the patch!
>>
>> On 23.03.2021 10:36, Mergen Imeev via Tarantool-patches wrote:
>>> This patch introduces mem_get_unsigned() function which is used to
>>> receive unsigned value from MEM.
>>>
>>> Part of #5818
>>> ---
>>>  src/box/sql/func.c    | 16 +++++++++++-----
>>>  src/box/sql/mem.c     | 37 +++++++++++++++++++++++++++----------
>>>  src/box/sql/mem.h     |  6 +++---
>>>  src/box/sql/sqlInt.h  |  3 ---
>>>  src/box/sql/vdbeapi.c |  6 ------
>>>  5 files changed, 41 insertions(+), 27 deletions(-)
>>>
>>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>>> index 0fa0f6ac7..a851d98f2 100644
>>> --- a/src/box/sql/func.c
>>> +++ b/src/box/sql/func.c
>>> @@ -118,9 +118,12 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
>>>  			luaL_pushint64(L, n);
>>>  			break;
>>>  		}
>>> -		case MP_UINT:
>>> -			luaL_pushuint64(L, sql_value_uint64(param));
>>> +		case MP_UINT: {
>>> +			uint64_t u;
>>> +			mem_get_unsigned(param, &u);
>>> +			luaL_pushuint64(L, u);
>> Maybe we could make 2 functions? One to get the value and ignore
>> the errors, and the other to get as an out parameter + return an
>> error?
>>
>> For instance, mem_to_uint() - returns uint64_t and internally asserts
>> that the value is correct. And mem_get_uint() works like your version.
>>
>> The same for the other get functions whose result is often ignored.
> For some functions I created a "proxy" functions in func.c the way you
> described, but not for this function since it is only used in a few places of
> sql/func.c. Should I do this for all functions? In func.c I mean. I see this as
> temporary measure, since I hope we will rework built-in functions one day.

Unfortunately, 'hope' is not enough. And it is highly possible the code
will live for long. Therefore I think we need to make it solid where possible
and clearly state it is unsafe or add assertions where it is not possible.

Here mem_get_uint() result is ignored always. Even if it fails. I think it
must be called something like mem_get_uint_unsafe() and return the uint as
'return', not via an out argument. Then at least we would see it is broken
when we are around this code again, and it won't raise questions if it is a
known issue, and why it is not fixed (this must be in a comment for the
function).

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

* Re: [Tarantool-patches] [PATCH v5 47/52] sql: introduce mem_get_double()
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 47/52] sql: introduce mem_get_double() Mergen Imeev via Tarantool-patches
@ 2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-14  1:00     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-13 23:04 UTC (permalink / raw)
  To: imeevma, tsafin; +Cc: tarantool-patches

The same as for the previous getters. The result is ignored in 100%
cases. Therefore it must be called 'unsafe', and return the double
as 'return'. When fails, it can return 0, so as at least the behaviour
is not undefined. The same '0 on error' could be used in the other
getters.

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

* Re: [Tarantool-patches] [PATCH v5 48/52] sql: introduce mem_get_bool()
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 48/52] sql: introduce mem_get_bool() Mergen Imeev via Tarantool-patches
@ 2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-14  1:29     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-13 23:04 UTC (permalink / raw)
  To: imeevma, tsafin; +Cc: tarantool-patches

The same as for the previous getters.

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

* Re: [Tarantool-patches] [PATCH v5 49/52] sql: introduce mem_get_str0() and mem_as_str0()
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 49/52] sql: introduce mem_get_str0() and mem_as_str0() Mergen Imeev via Tarantool-patches
@ 2021-04-13 23:06   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-14  1:43     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-13 23:06 UTC (permalink / raw)
  To: imeevma, tsafin; +Cc: tarantool-patches

Thanks for the discussion!

See 2 comments below.

1. With this getter it is less trivial than with the previous
ones.

I just realized, that we call mem_as_str() on function arguments.
What if a mem is passed to multiple functions, and one of them
changes it affecting the other functions? What if the same function
is called multiple times during a query? - the first execution
affects the argument for the next executions? Does not look right.
Am I missing something?

It seems that could be fixed not so hard.

If the value is already a 0-terminated string, we return it.
If it is a blob or not terminated string, we extended it, add 0,
and return. It does not really change the original value anyway.
Mem.n stays the same, and the first n bytes stay the same.
If it is a number, we can save its string representation to
Mem.z, which is not used for numbers.

Will it work?

> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index e48e8788c..5848ae729 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -568,6 +568,28 @@ mem_get_double(const struct Mem *mem, double *d);
>  int
>  mem_get_bool(const struct Mem *mem, bool *b);
>  
> +/**
> + * Return value for MEM of STRING type if MEM contains a NULL-terminated string.
> + * Otherwise convert value of the MEM to NULL-terminated string if possible and
> + * return converted value. Original MEM is not changed.
> + */
> +int
> +mem_get_str0(const struct Mem *mem, const char **s);
> +
> +/**
> + * Return value for MEM of STRING type if MEM contains NULL-terminated string.
> + * Otherwise convert MEM to MEM of string type that contains NULL-terminated
> + * string and return its value. Return NULL if conversion is impossible.
> + */
> +static inline const char *
> +mem_as_str0(struct Mem *mem)
> +{
> + const char *str;
> + if (mem_to_str0(mem) != 0 || mem_get_str0(mem, &str) != 0)

2. If mem_to_str0 succeeded, why can't you return mem->z right away?

> +   return NULL;
> + return str;
> +}

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

* Re: [Tarantool-patches] [PATCH v5 51/52] sql: introduce mem_get_bytes_len()
  2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 51/52] sql: introduce mem_get_bytes_len() Mergen Imeev via Tarantool-patches
@ 2021-04-13 23:06   ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-14  1:55     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-13 23:06 UTC (permalink / raw)
  To: imeevma, tsafin; +Cc: tarantool-patches

Good job on the patch!

> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 2896a5c31..746bda0f4 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -65,6 +65,14 @@ mem_as_bin(struct Mem *mem)
>  	return s;
>  }
>  
> +static int
> +mem_get_length(struct Mem *mem)
> +{
> +	uint32_t len;
> +	mem_get_bytes_len(mem, &len);

mem_get_bytes_len() is never used except here. I suggest you to
rename it to mem_len(), drop mem_get_length(), and use mem_len()
everywhere.

Since you ignore the error always, it probably must has 'unsafe'
suffix to emphasize it is broken.

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

* Re: [Tarantool-patches] [PATCH v5 41/52] sql: introduce mem_to_number()
  2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 41/52] sql: introduce mem_to_number() Mergen Imeev via Tarantool-patches
@ 2021-04-13 23:25   ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-13 23:25 UTC (permalink / raw)
  To: v.shpilevoy, tsafin, tarantool-patches

Thank you for the review! Added assert. Diff and new patch below.

On Fri, Apr 09, 2021 at 11:53:38PM +0300, Mergen Imeev via Tarantool-patches wrote:
> This patch introduces mem_to_number(). This function is used to convert
> MEM to MEM contains number.
> 
> Part of #5818
> ---
>  src/box/sql/func.c |  2 +-
>  src/box/sql/mem.c  | 60 +++++++++++++---------------------------------
>  src/box/sql/mem.h  | 32 ++++++-------------------
>  src/box/sql/vdbe.c |  2 +-
>  4 files changed, 25 insertions(+), 71 deletions(-)
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 0b85bf365..0282aec74 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -1641,7 +1641,7 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
>  	if (type == MP_NIL || p == NULL)
>  		return;
>  	if (type != MP_DOUBLE && type != MP_INT && type != MP_UINT) {
> -		if (mem_apply_numeric_type(argv[0]) != 0) {
> +		if (type != MP_STR || mem_to_number(argv[0]) != 0) {
>  			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
>  				 mem_str(argv[0]), "number");
>  			context->is_aborted = true;
> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index 75d4c4d18..9a0234e60 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -807,6 +807,21 @@ mem_to_double(struct Mem *mem)
>  	return -1;
>  }
>  
> +int
> +mem_to_number(struct Mem *mem)
> +{
> +	if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
> +		return 0;
> +	if ((mem->flags & MEM_Bool) != 0)
> +		return bool_to_int(mem);
> +	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
> +		if (bytes_to_int(mem) == 0)
> +			return 0;
> +		return bytes_to_double(mem);
> +	}
> +	return -1;
> +}
> +
>  int
>  mem_copy(struct Mem *to, const struct Mem *from)
>  {
> @@ -1879,49 +1894,6 @@ registerTrace(int iReg, Mem *p) {
>  }
>  #endif
>  
> -int
> -mem_apply_numeric_type(struct Mem *record)
> -{
> -	if ((record->flags & MEM_Str) == 0)
> -		return -1;
> -	int64_t integer_value;
> -	bool is_neg;
> -	if (sql_atoi64(record->z, &integer_value, &is_neg, record->n) == 0) {
> -		mem_set_int(record, integer_value, is_neg);
> -		return 0;
> -	}
> -	double float_value;
> -	if (sqlAtoF(record->z, &float_value, record->n) == 0)
> -		return -1;
> -	mem_set_double(record, float_value);
> -	return 0;
> -}
> -
> -int
> -vdbe_mem_numerify(struct Mem *mem)
> -{
> -	if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real | MEM_Null)) != 0)
> -		return 0;
> -	if ((mem->flags & MEM_Bool) != 0) {
> -		mem->u.u = (uint64_t)mem->u.b;
> -		mem->flags = MEM_UInt;
> -		mem->field_type = FIELD_TYPE_UNSIGNED;
> -		return 0;
> -	}
> -	assert((mem->flags & (MEM_Blob | MEM_Str)) != 0);
> -	bool is_neg;
> -	int64_t i;
> -	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) == 0) {
> -		mem_set_int(mem, i, is_neg);
> -	} else {
> -		double d;
> -		if (sqlAtoF(mem->z, &d, mem->n) == 0)
> -			return -1;
> -		mem_set_double(mem, d);
> -	}
> -	return 0;
> -}
> -
>  /*
>   * Cast the datatype of the value in pMem according to the type
>   * @type.  Casting is different from applying type in that a cast
> @@ -2002,7 +1974,7 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type)
>  	case FIELD_TYPE_DOUBLE:
>  		return mem_to_double(pMem);
>  	case FIELD_TYPE_NUMBER:
> -		return vdbe_mem_numerify(pMem);
> +		return mem_to_number(pMem);
>  	case FIELD_TYPE_VARBINARY:
>  		if ((pMem->flags & MEM_Blob) != 0)
>  			return 0;
> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index bf8c0f3b5..90f46af80 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -498,6 +498,13 @@ mem_to_int_precise(struct Mem *mem);
>  int
>  mem_to_double(struct Mem *mem);
>  
> +/**
> + * Convert the given MEM to NUMBER. This function defines the rules that are
> + * used to convert values of all other types to NUMBER.
> + */
> +int
> +mem_to_number(struct Mem *mem);
> +
>  /**
>   * Simple type to str convertor. It is used to simplify
>   * error reporting.
> @@ -531,31 +538,6 @@ registerTrace(int iReg, Mem *p);
>  #define memIsValid(M) !mem_is_invalid(M)
>  #endif
>  
> -/**
> - * 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 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.
> - * @retval -1 Otherwise.
> - */
> -int
> -mem_apply_numeric_type(struct Mem *record);
> -
> -/**
> - * Convert @a mem to NUMBER type, so that after conversion it has
> - * one of types MEM_Real, MEM_Int or MEM_UInt. If conversion is
> - * not possible, function returns -1.
> - *
> - * Beware - this function changes value and type of @a mem
> - * argument.
> - */
> -int
> -vdbe_mem_numerify(struct Mem *mem);
> -
>  int sqlVdbeMemCast(struct Mem *, enum field_type type);
>  int sqlVdbeMemStringify(struct Mem *);
>  int sqlVdbeMemNulTerminate(struct Mem *);
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 90a901555..7880af248 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -2712,7 +2712,7 @@ case OP_SeekGT: {       /* jump, in3 */
>  		if (mem_is_null(pIn3))
>  			goto skip_truncate;
>  		if (mem_is_str(pIn3))
> -			mem_apply_numeric_type(pIn3);
> +			mem_to_number(pIn3);
>  		int64_t i;
>  		if (mem_is_uint(pIn3)) {
>  			i = pIn3->u.u;
> -- 
> 2.25.1
> 


Diff:


diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 0c234b18b..990e261b7 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -618,6 +618,7 @@ mem_to_double(struct Mem *mem)
 int
 mem_to_number(struct Mem *mem)
 {
+	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
 	if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
 		return 0;
 	if ((mem->flags & MEM_Bool) != 0)



New patch:


commit fba444e64b55308f20df87d44ca31f807180cf10
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 11:39:13 2021 +0300

    sql: introduce mem_to_number()
    
    This patch introduces mem_to_number(). This function is used to convert
    MEM to MEM contains number.
    
    Part of #5818

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 0b85bf365..0282aec74 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1641,7 +1641,7 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 	if (type == MP_NIL || p == NULL)
 		return;
 	if (type != MP_DOUBLE && type != MP_INT && type != MP_UINT) {
-		if (mem_apply_numeric_type(argv[0]) != 0) {
+		if (type != MP_STR || mem_to_number(argv[0]) != 0) {
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
 				 mem_str(argv[0]), "number");
 			context->is_aborted = true;
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 3685bf6b9..990e261b7 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -615,6 +615,22 @@ mem_to_double(struct Mem *mem)
 	return -1;
 }
 
+int
+mem_to_number(struct Mem *mem)
+{
+	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
+	if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
+		return 0;
+	if ((mem->flags & MEM_Bool) != 0)
+		return bool_to_int(mem);
+	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
+		if (bytes_to_int(mem) == 0)
+			return 0;
+		return bytes_to_double(mem);
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -1652,49 +1668,6 @@ registerTrace(int iReg, Mem *p) {
 }
 #endif
 
-int
-mem_apply_numeric_type(struct Mem *record)
-{
-	if ((record->flags & MEM_Str) == 0)
-		return -1;
-	int64_t integer_value;
-	bool is_neg;
-	if (sql_atoi64(record->z, &integer_value, &is_neg, record->n) == 0) {
-		mem_set_int(record, integer_value, is_neg);
-		return 0;
-	}
-	double float_value;
-	if (sqlAtoF(record->z, &float_value, record->n) == 0)
-		return -1;
-	mem_set_double(record, float_value);
-	return 0;
-}
-
-int
-vdbe_mem_numerify(struct Mem *mem)
-{
-	if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real | MEM_Null)) != 0)
-		return 0;
-	if ((mem->flags & MEM_Bool) != 0) {
-		mem->u.u = (uint64_t)mem->u.b;
-		mem->flags = MEM_UInt;
-		mem->field_type = FIELD_TYPE_UNSIGNED;
-		return 0;
-	}
-	assert((mem->flags & (MEM_Blob | MEM_Str)) != 0);
-	bool is_neg;
-	int64_t i;
-	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) == 0) {
-		mem_set_int(mem, i, is_neg);
-	} else {
-		double d;
-		if (sqlAtoF(mem->z, &d, mem->n) == 0)
-			return -1;
-		mem_set_double(mem, d);
-	}
-	return 0;
-}
-
 /*
  * Cast the datatype of the value in pMem according to the type
  * @type.  Casting is different from applying type in that a cast
@@ -1775,7 +1748,7 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type)
 	case FIELD_TYPE_DOUBLE:
 		return mem_to_double(pMem);
 	case FIELD_TYPE_NUMBER:
-		return vdbe_mem_numerify(pMem);
+		return mem_to_number(pMem);
 	case FIELD_TYPE_VARBINARY:
 		if ((pMem->flags & MEM_Blob) != 0)
 			return 0;
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 485a6fed0..85ad94ab3 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -708,6 +708,13 @@ mem_to_int_precise(struct Mem *mem);
 int
 mem_to_double(struct Mem *mem);
 
+/**
+ * Convert the given MEM to NUMBER. This function defines the rules that are
+ * used to convert values of all other types to NUMBER.
+ */
+int
+mem_to_number(struct Mem *mem);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -741,31 +748,6 @@ registerTrace(int iReg, Mem *p);
 #define memIsValid(M)  ((M)->flags & MEM_Undefined)==0
 #endif
 
-/**
- * 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 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.
- * @retval -1 Otherwise.
- */
-int
-mem_apply_numeric_type(struct Mem *record);
-
-/**
- * Convert @a mem to NUMBER type, so that after conversion it has
- * one of types MEM_Real, MEM_Int or MEM_UInt. If conversion is
- * not possible, function returns -1.
- *
- * Beware - this function changes value and type of @a mem
- * argument.
- */
-int
-vdbe_mem_numerify(struct Mem *mem);
-
 int sqlVdbeMemCast(struct Mem *, enum field_type type);
 int sqlVdbeMemStringify(struct Mem *);
 int sqlVdbeMemNulTerminate(struct Mem *);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 008148687..de6455c50 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2757,7 +2757,7 @@ case OP_SeekGT: {       /* jump, in3 */
 		if (mem_is_null(pIn3))
 			goto skip_truncate;
 		if (mem_is_str(pIn3))
-			mem_apply_numeric_type(pIn3);
+			mem_to_number(pIn3);
 		int64_t i;
 		if (mem_is_uint(pIn3)) {
 			i = pIn3->u.u;

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

* Re: [Tarantool-patches] [PATCH v5 42/52] sql: introduce mem_to_str() and mem_to_str0()
  2021-04-13 22:58   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-04-13 23:41     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-13 23:41 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer, diff and new patch below. Also, I moved
<type>_to_<type>() functions to group them.


On Wed, Apr 14, 2021 at 12:58:05AM +0200, Vladislav Shpilevoy wrote:
> Thanks for working on this!
> 
> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index 9a0234e60..be7b47e76 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -822,6 +822,130 @@ mem_to_number(struct Mem *mem)
> 
> <...>
> 
> > +
> > +int
> > +mem_to_str0(struct Mem *mem)
> > +{
> > +	if ((mem->flags & (MEM_Str | MEM_Term)) == (MEM_Str | MEM_Term))
> > +		return 0;
> > +	if ((mem->flags & MEM_Str) != 0)
> > +		return str_to_str0(mem);
> > +	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> > +		return int_to_str0(mem);
> > +	if ((mem->flags & MEM_Real) != 0)
> > +		return double_to_str0(mem);
> > +	if ((mem->flags & MEM_Bool) != 0)
> > +		return bool_to_str0(mem);
> > +	if ((mem->flags & MEM_Blob) != 0) {
> > +		if ((mem->flags & MEM_Subtype) == 0)
> > +			return bin_to_str0(mem);
> > +		if (mp_typeof(*mem->z) == MP_MAP)
> > +			return map_to_str0(mem);
> > +		return array_to_str0(mem);
> > +	}
> > +	return -1;
> > +}
> > +
> > +int
> > +mem_to_str(struct Mem *mem)
> > +{
> > +	if ((mem->flags & MEM_Str) != 0)
> > +		return 0;
> > +	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> > +		return int_to_str0(mem);
> > +	if ((mem->flags & MEM_Real) != 0)
> > +		return double_to_str0(mem);
> > +	if ((mem->flags & MEM_Bool) != 0)
> > +		return bool_to_str0(mem);
> > +	if ((mem->flags & MEM_Blob) != 0) {
> > +		if ((mem->flags & MEM_Subtype) == 0)
> > +			return bin_to_str(mem);
> > +		if (mp_typeof(*mem->z) == MP_MAP)
> > +			return map_to_str0(mem);
> > +		return array_to_str0(mem);
> > +	}
> > +	return -1;
> 
> In the old function there was an assertion that only simple types
> are passed (no Aggs, Frames). Please, keep it, or add a diag_set()
> here. The same above.
Added.



Diff:

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 761abbc2c..830fc3840 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -506,6 +506,53 @@ int_to_double(struct Mem *mem)
 	return 0;
 }
 
+static inline int
+int_to_str0(struct Mem *mem)
+{
+	const char *str;
+	if ((mem->flags & MEM_UInt) != 0)
+		str = tt_sprintf("%llu", mem->u.u);
+	else
+		str = tt_sprintf("%lld", mem->u.i);
+	return mem_copy_str0(mem, str);
+}
+
+static inline int
+str_to_str0(struct Mem *mem)
+{
+	assert((mem->flags | MEM_Str) != 0);
+	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
+		return -1;
+	mem->z[mem->n] = '\0';
+	mem->flags |= MEM_Term;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
+static inline int
+bin_to_str(struct Mem *mem)
+{
+	if (ExpandBlob(mem) != 0)
+		return -1;
+	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
+		      MEM_Str;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
+static inline int
+bin_to_str0(struct Mem *mem)
+{
+	if (ExpandBlob(mem) != 0)
+		return -1;
+	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
+		return -1;
+	mem->z[mem->n] = '\0';
+	mem->flags = MEM_Str | MEM_Term;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
 static inline int
 bytes_to_int(struct Mem *mem)
 {
@@ -565,6 +612,18 @@ double_to_int_precise(struct Mem *mem)
 	return -1;
 }
 
+static inline int
+double_to_str0(struct Mem *mem)
+{
+	if (sqlVdbeMemGrow(mem, BUF_SIZE, 0) != 0)
+		return -1;
+	sql_snprintf(BUF_SIZE, mem->z, "%!.15g", mem->u.r);
+	mem->n = strlen(mem->z);
+	mem->flags = MEM_Str | MEM_Term;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
 static inline int
 bool_to_int(struct Mem *mem)
 {
@@ -574,6 +633,27 @@ bool_to_int(struct Mem *mem)
 	return 0;
 }
 
+static inline int
+bool_to_str0(struct Mem *mem)
+{
+	const char *str = mem->u.b ? "TRUE" : "FALSE";
+	return mem_copy_str0(mem, str);
+}
+
+static inline int
+array_to_str0(struct Mem *mem)
+{
+	const char *str = mp_str(mem->z);
+	return mem_copy_str0(mem, str);
+}
+
+static inline int
+map_to_str0(struct Mem *mem)
+{
+	const char *str = mp_str(mem->z);
+	return mem_copy_str0(mem, str);
+}
+
 int
 mem_to_int(struct Mem *mem)
 {
@@ -631,89 +711,10 @@ mem_to_number(struct Mem *mem)
 	return -1;
 }
 
-static inline int
-int_to_str0(struct Mem *mem)
-{
-	const char *str;
-	if ((mem->flags & MEM_UInt) != 0)
-		str = tt_sprintf("%llu", mem->u.u);
-	else
-		str = tt_sprintf("%lld", mem->u.i);
-	return mem_copy_str0(mem, str);
-}
-
-static inline int
-array_to_str0(struct Mem *mem)
-{
-	const char *str = mp_str(mem->z);
-	return mem_copy_str0(mem, str);
-}
-
-static inline int
-map_to_str0(struct Mem *mem)
-{
-	const char *str = mp_str(mem->z);
-	return mem_copy_str0(mem, str);
-}
-
-static inline int
-bool_to_str0(struct Mem *mem)
-{
-	const char *str = mem->u.b ? "TRUE" : "FALSE";
-	return mem_copy_str0(mem, str);
-}
-
-static inline int
-bin_to_str(struct Mem *mem)
-{
-	if (ExpandBlob(mem) != 0)
-		return -1;
-	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
-		      MEM_Str;
-	mem->field_type = FIELD_TYPE_STRING;
-	return 0;
-}
-
-static inline int
-bin_to_str0(struct Mem *mem)
-{
-	if (ExpandBlob(mem) != 0)
-		return -1;
-	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
-		return -1;
-	mem->z[mem->n] = '\0';
-	mem->flags = MEM_Str | MEM_Term;
-	mem->field_type = FIELD_TYPE_STRING;
-	return 0;
-}
-
-static inline int
-double_to_str0(struct Mem *mem)
-{
-	if (sqlVdbeMemGrow(mem, BUF_SIZE, 0) != 0)
-		return -1;
-	sql_snprintf(BUF_SIZE, mem->z, "%!.15g", mem->u.r);
-	mem->n = strlen(mem->z);
-	mem->flags = MEM_Str | MEM_Term;
-	mem->field_type = FIELD_TYPE_STRING;
-	return 0;
-}
-
-static inline int
-str_to_str0(struct Mem *mem)
-{
-	assert((mem->flags | MEM_Str) != 0);
-	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
-		return -1;
-	mem->z[mem->n] = '\0';
-	mem->flags |= MEM_Term;
-	mem->field_type = FIELD_TYPE_STRING;
-	return 0;
-}
-
 int
 mem_to_str0(struct Mem *mem)
 {
+	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
 	if ((mem->flags & (MEM_Str | MEM_Term)) == (MEM_Str | MEM_Term))
 		return 0;
 	if ((mem->flags & MEM_Str) != 0)
@@ -737,6 +738,7 @@ mem_to_str0(struct Mem *mem)
 int
 mem_to_str(struct Mem *mem)
 {
+	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
 	if ((mem->flags & MEM_Str) != 0)
 		return 0;
 	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)



New patch:


commit 46a840d827f10f69cfcd7fd0075aa09ddd27ccc8
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 11:55:48 2021 +0300

    sql: introduce mem_to_str() and mem_to_str0()
    
    This patch introduces mem_to_str() and mem_to_str0() functions. These
    functions are used to convert a MEM to a MEM that contains string value.
    These functions defines the rules that are used during convertion from
    values of all other types to STRING.
    
    Part of #5818

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 990e261b7..830fc3840 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -506,6 +506,53 @@ int_to_double(struct Mem *mem)
 	return 0;
 }
 
+static inline int
+int_to_str0(struct Mem *mem)
+{
+	const char *str;
+	if ((mem->flags & MEM_UInt) != 0)
+		str = tt_sprintf("%llu", mem->u.u);
+	else
+		str = tt_sprintf("%lld", mem->u.i);
+	return mem_copy_str0(mem, str);
+}
+
+static inline int
+str_to_str0(struct Mem *mem)
+{
+	assert((mem->flags | MEM_Str) != 0);
+	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
+		return -1;
+	mem->z[mem->n] = '\0';
+	mem->flags |= MEM_Term;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
+static inline int
+bin_to_str(struct Mem *mem)
+{
+	if (ExpandBlob(mem) != 0)
+		return -1;
+	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
+		      MEM_Str;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
+static inline int
+bin_to_str0(struct Mem *mem)
+{
+	if (ExpandBlob(mem) != 0)
+		return -1;
+	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
+		return -1;
+	mem->z[mem->n] = '\0';
+	mem->flags = MEM_Str | MEM_Term;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
 static inline int
 bytes_to_int(struct Mem *mem)
 {
@@ -565,6 +612,18 @@ double_to_int_precise(struct Mem *mem)
 	return -1;
 }
 
+static inline int
+double_to_str0(struct Mem *mem)
+{
+	if (sqlVdbeMemGrow(mem, BUF_SIZE, 0) != 0)
+		return -1;
+	sql_snprintf(BUF_SIZE, mem->z, "%!.15g", mem->u.r);
+	mem->n = strlen(mem->z);
+	mem->flags = MEM_Str | MEM_Term;
+	mem->field_type = FIELD_TYPE_STRING;
+	return 0;
+}
+
 static inline int
 bool_to_int(struct Mem *mem)
 {
@@ -574,6 +633,27 @@ bool_to_int(struct Mem *mem)
 	return 0;
 }
 
+static inline int
+bool_to_str0(struct Mem *mem)
+{
+	const char *str = mem->u.b ? "TRUE" : "FALSE";
+	return mem_copy_str0(mem, str);
+}
+
+static inline int
+array_to_str0(struct Mem *mem)
+{
+	const char *str = mp_str(mem->z);
+	return mem_copy_str0(mem, str);
+}
+
+static inline int
+map_to_str0(struct Mem *mem)
+{
+	const char *str = mp_str(mem->z);
+	return mem_copy_str0(mem, str);
+}
+
 int
 mem_to_int(struct Mem *mem)
 {
@@ -631,6 +711,52 @@ mem_to_number(struct Mem *mem)
 	return -1;
 }
 
+int
+mem_to_str0(struct Mem *mem)
+{
+	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
+	if ((mem->flags & (MEM_Str | MEM_Term)) == (MEM_Str | MEM_Term))
+		return 0;
+	if ((mem->flags & MEM_Str) != 0)
+		return str_to_str0(mem);
+	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		return int_to_str0(mem);
+	if ((mem->flags & MEM_Real) != 0)
+		return double_to_str0(mem);
+	if ((mem->flags & MEM_Bool) != 0)
+		return bool_to_str0(mem);
+	if ((mem->flags & MEM_Blob) != 0) {
+		if ((mem->flags & MEM_Subtype) == 0)
+			return bin_to_str0(mem);
+		if (mp_typeof(*mem->z) == MP_MAP)
+			return map_to_str0(mem);
+		return array_to_str0(mem);
+	}
+	return -1;
+}
+
+int
+mem_to_str(struct Mem *mem)
+{
+	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
+	if ((mem->flags & MEM_Str) != 0)
+		return 0;
+	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		return int_to_str0(mem);
+	if ((mem->flags & MEM_Real) != 0)
+		return double_to_str0(mem);
+	if ((mem->flags & MEM_Bool) != 0)
+		return bool_to_str0(mem);
+	if ((mem->flags & MEM_Blob) != 0) {
+		if ((mem->flags & MEM_Subtype) == 0)
+			return bin_to_str(mem);
+		if (mp_typeof(*mem->z) == MP_MAP)
+			return map_to_str0(mem);
+		return array_to_str0(mem);
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -1291,7 +1417,7 @@ valueToText(sql_value * pVal)
 		pVal->flags |= MEM_Str;
 		sqlVdbeMemNulTerminate(pVal);	/* IMP: R-31275-44060 */
 	} else {
-		sqlVdbeMemStringify(pVal);
+		mem_to_str(pVal);
 		assert(0 == (1 & SQL_PTR_TO_INT(pVal->z)));
 	}
 	return pVal->z;
@@ -1775,74 +1901,6 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type)
 	}
 }
 
-/*
- * Add MEM_Str to the set of representations for the given Mem.  Numbers
- * are converted using sql_snprintf().  Converting a BLOB to a string
- * is a no-op.
- *
- * Existing representations MEM_Int and MEM_Real are invalidated if
- * bForce is true but are retained if bForce is false.
- *
- * A MEM_Null value will never be passed to this function. This function is
- * used for converting values to text for returning to the user (i.e. via
- * sql_value_text()), or for ensuring that values to be used as btree
- * keys are strings. In the former case a NULL pointer is returned the
- * user and the latter is an internal programming error.
- */
-int
-sqlVdbeMemStringify(Mem * pMem)
-{
-	int fg = pMem->flags;
-	int nByte = 32;
-
-	if ((fg & (MEM_Null | MEM_Str | MEM_Blob)) != 0 &&
-	    !mem_has_msgpack_subtype(pMem))
-		return 0;
-
-	assert(!(fg & MEM_Zero));
-	assert((fg & (MEM_Int | MEM_UInt | MEM_Real | MEM_Bool |
-		      MEM_Blob)) != 0);
-	assert(EIGHT_BYTE_ALIGNMENT(pMem));
-
-	/*
-	 * In case we have ARRAY/MAP we should save decoded value
-	 * before clearing pMem->z.
-	 */
-	char *value = NULL;
-	if (mem_has_msgpack_subtype(pMem)) {
-		const char *value_str = mp_str(pMem->z);
-		nByte = strlen(value_str) + 1;
-		value = region_alloc(&fiber()->gc, nByte);
-		memcpy(value, value_str, nByte);
-	}
-
-	if (sqlVdbeMemClearAndResize(pMem, nByte)) {
-		return -1;
-	}
-	if (fg & MEM_Int) {
-		sql_snprintf(nByte, pMem->z, "%lld", pMem->u.i);
-		pMem->flags &= ~MEM_Int;
-	} else if ((fg & MEM_UInt) != 0) {
-		sql_snprintf(nByte, pMem->z, "%llu", pMem->u.u);
-		pMem->flags &= ~MEM_UInt;
-	} else if ((fg & MEM_Bool) != 0) {
-		sql_snprintf(nByte, pMem->z, "%s",
-			     SQL_TOKEN_BOOLEAN(pMem->u.b));
-		pMem->flags &= ~MEM_Bool;
-	} else if (mem_has_msgpack_subtype(pMem)) {
-		sql_snprintf(nByte, pMem->z, "%s", value);
-		pMem->flags &= ~MEM_Subtype;
-		pMem->subtype = SQL_SUBTYPE_NO;
-	} else {
-		assert(fg & MEM_Real);
-		sql_snprintf(nByte, pMem->z, "%!.15g", pMem->u.r);
-		pMem->flags &= ~MEM_Real;
-	}
-	pMem->n = sqlStrlen30(pMem->z);
-	pMem->flags |= MEM_Str | MEM_Term;
-	return 0;
-}
-
 /*
  * Make sure the given Mem is \u0000 terminated.
  */
@@ -1965,7 +2023,7 @@ mem_apply_type(struct Mem *record, enum field_type type)
 		 */
 		if ((record->flags & MEM_Str) == 0 &&
 		    (record->flags & (MEM_Real | MEM_Int | MEM_UInt)) != 0)
-			sqlVdbeMemStringify(record);
+			mem_to_str(record);
 		record->flags &= ~(MEM_Real | MEM_Int | MEM_UInt);
 		return 0;
 	case FIELD_TYPE_VARBINARY:
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 85ad94ab3..454553d92 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -715,6 +715,23 @@ mem_to_double(struct Mem *mem);
 int
 mem_to_number(struct Mem *mem);
 
+/**
+ * Convert the given MEM to STRING. This function and the function below define
+ * the rules that are used to convert values of all other types to STRING. In
+ * this function, the string received after the convertion may be not
+ * NULL-terminated.
+ */
+int
+mem_to_str(struct Mem *mem);
+
+/**
+ * Convert the given MEM to STRING. This function and the function above define
+ * the rules that are used to convert values of all other types to STRING. In
+ * this function, the string received after convertion is NULL-terminated.
+ */
+int
+mem_to_str0(struct Mem *mem);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -749,7 +766,6 @@ registerTrace(int iReg, Mem *p);
 #endif
 
 int sqlVdbeMemCast(struct Mem *, enum field_type type);
-int sqlVdbeMemStringify(struct Mem *);
 int sqlVdbeMemNulTerminate(struct Mem *);
 int sqlVdbeMemExpandBlob(struct Mem *);
 #define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlVdbeMemExpandBlob(P):0)

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

* Re: [Tarantool-patches] [PATCH v5 43/52] sql: introduce mem_cast_explicit()
  2021-04-13 22:59   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-04-14  0:01     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-14  0:01 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers, diff and new patch below.

On Wed, Apr 14, 2021 at 12:59:31AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> See 2 comments below.
> 
> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index be7b47e76..45d2d5fe3 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -946,6 +946,131 @@ mem_to_str(struct Mem *mem)
> >  	return -1;
> >  }
> >  
> > +static inline int
> > +bytes_to_uint(struct Mem *mem)
> > +{
> > +	bool is_neg;
> > +	int64_t i;
> > +	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
> > +		return -1;
> > +	if (is_neg)
> > +		return -1;
> > +	mem_set_uint(mem, (uint64_t)i);
> > +	return 0;
> > +}
> > +
> > +static inline int
> > +str_to_bool(struct Mem *mem)
> > +{
> > +	char *str = mem->z;
> > +	bool b;
> > +	const char *str_true = "TRUE";
> > +	const char *str_false = "FALSE";
> > +	uint32_t len_true = strlen(str_true);
> > +	uint32_t len_false = strlen(str_false);
> > +
> > +	for (; str[0] == ' '; str++);
> > +	if (strncasecmp(str, str_true, len_true) == 0) {
> > +		b = true;
> > +		str += len_true;
> > +	} else if (strncasecmp(str, str_false, len_false) == 0) {
> > +		b = false;
> > +		str += len_false;
> > +	} else {
> > +		return -1;
> > +	}
> > +	for (; str[0] == ' '; str++);
> > +	if (str[0] != '\0')
> > +		return -1;
> > +	mem_set_bool(mem, b);
> > +	return 0;
> > +}
> > +
> > +static inline int
> > +int_to_bool(struct Mem *mem)
> > +{
> > +	mem->u.b = mem->u.i != 0;
> > +	mem->flags = MEM_Bool;
> > +	mem->field_type = FIELD_TYPE_BOOLEAN;
> > +	return 0;
> > +}
> > +
> > +static inline int
> > +double_to_bool(struct Mem *mem)
> > +{
> > +	mem->u.b = mem->u.r != 0.;
> > +	mem->flags = MEM_Bool;
> > +	mem->field_type = FIELD_TYPE_BOOLEAN;
> > +	return 0;
> > +}
> > +
> > +static inline int
> > +str_to_bin(struct Mem *mem)
> > +{
> > +	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
> > +		     MEM_Blob;
> > +	mem->field_type = FIELD_TYPE_VARBINARY;
> > +	return 0;
> > +}
> 
> 1. You have tons of <src>_to_<dst> converters. I propose you to group them.
> For example, str_to_* all together, double_to_* all together, and so on.
> It would simplify reading and search.
> 
Fixed. Now the are in some order. I tried to keep sorting by second type too,
but not sure that I succeed.

> > +
> > +int
> > +mem_cast_explicit(struct Mem *mem, enum field_type type)
> > +{
> > +	if ((mem->flags & MEM_Null) != 0) {
> > +		mem->field_type = type;
> > +		return 0;
> > +	}
> > +	switch (type) {
> > +	case FIELD_TYPE_UNSIGNED:
> > +		if ((mem->flags & MEM_UInt) != 0)
> > +			return 0;
> > +		if ((mem->flags & MEM_Int) != 0)
> > +			return -1;
> > +		if ((mem->flags & MEM_Blob) != 0 &&
> > +		    (mem->flags & MEM_Subtype) != 0)
> > +			return -1;
> > +		if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
> > +			return bytes_to_uint(mem);
> > +		if ((mem->flags & MEM_Real) != 0)
> > +			return double_to_int(mem);
> 
> 2. tarantool> box.execute('SELECT CAST(-1.1 AS UNSIGNED);')
> ---
> - metadata:
>   - name: COLUMN_1
>     type: unsigned
>   rows:
>   - [-1]
> ...
> 
> That looks quite broken. Is this a known issue? From the
> code I see the issue existed before your patch (but I was
> too lazy to try it).
> 
I knew about it, though I did not see an issue on GH. I will fill one later,
if I will find nothing.

> > @@ -2018,113 +2107,6 @@ registerTrace(int iReg, Mem *p) {
> >  }
> >  #endif
> >  
> > -/*
> > - * Cast the datatype of the value in pMem according to the type
> > - * @type.  Casting is different from applying type in that a cast
> > - * is forced.  In other words, the value is converted into the desired
> > - * type even if that results in loss of data.  This routine is
> > - * used (for example) to implement the SQL "cast()" operator.
> > - */
> > -int
> > -sqlVdbeMemCast(Mem * pMem, enum field_type type)
> > -{
> > -	assert(type < field_type_MAX);
> > -	if (pMem->flags & MEM_Null)
> > -		return 0;
> > -	switch (type) {
> > -	case FIELD_TYPE_SCALAR:
> > -		return 0;
> > -	case FIELD_TYPE_BOOLEAN:
> > -		if ((pMem->flags & MEM_Int) != 0) {
> > -			mem_set_bool(pMem, pMem->u.i);
> > -			return 0;
> > -		}
> > -		if ((pMem->flags & MEM_UInt) != 0) {
> > -			mem_set_bool(pMem, pMem->u.u);
> > -			return 0;
> > -		}
> > -		if ((pMem->flags & MEM_Real) != 0) {
> > -			mem_set_bool(pMem, pMem->u.r);
> > -			return 0;
> > -		}
> > -		if ((pMem->flags & MEM_Str) != 0) {
> > -			bool value;
> > -			if (str_cast_to_boolean(pMem->z, &value) != 0)
> > -				return -1;
> > -			mem_set_bool(pMem, value);
> > -			return 0;
> > -		}
> > -		if ((pMem->flags & MEM_Bool) != 0)
> > -			return 0;
> > -		return -1;
> > -	case FIELD_TYPE_INTEGER:
> > -	case FIELD_TYPE_UNSIGNED:
> > -		if ((pMem->flags & (MEM_Blob | MEM_Str)) != 0) {
> > -			bool is_neg;
> > -			int64_t val;
> > -			if (sql_atoi64(pMem->z, &val, &is_neg, pMem->n) != 0)
> > -				return -1;
> > -			if (type == FIELD_TYPE_UNSIGNED && is_neg)
> > -				return -1;
> > -			mem_set_int(pMem, val, is_neg);
> > -			return 0;
> > -		}
> > -		if ((pMem->flags & MEM_Bool) != 0) {
> > -			pMem->u.u = (uint64_t)pMem->u.b;
> > -			pMem->flags = MEM_UInt;
> > -			pMem->field_type = FIELD_TYPE_UNSIGNED;
> > -			return 0;
> > -		}
> > -		if ((pMem->flags & MEM_Real) != 0) {
> > -			double d = pMem->u.r;
> > -			if (d < 0. && d >= (double)INT64_MIN) {
> > -				pMem->u.i = (int64_t)d;
> > -				pMem->flags = MEM_Int;
> > -				pMem->field_type = FIELD_TYPE_INTEGER;
> > -				return 0;
> > -			}
> > -			if (d >= 0. && d < (double)UINT64_MAX) {
> > -				pMem->u.u = (uint64_t)d;
> > -				pMem->flags = MEM_UInt;
> > -				pMem->field_type = FIELD_TYPE_UNSIGNED;
> > -				return 0;
> > -			}
> > -			return -1;
> > -		}
> > -		if (type == FIELD_TYPE_UNSIGNED &&
> > -		    (pMem->flags & MEM_UInt) == 0)
> > -			return -1;
> > -		return 0;
> > -	case FIELD_TYPE_DOUBLE:
> > -		return mem_to_double(pMem);
> > -	case FIELD_TYPE_NUMBER:
> > -		return mem_to_number(pMem);
> > -	case FIELD_TYPE_VARBINARY:
> > -		if ((pMem->flags & MEM_Blob) != 0)
> > -			return 0;
> > -		if ((pMem->flags & MEM_Str) != 0) {
> > -			MemSetTypeFlag(pMem, MEM_Str);
> > -			return 0;
> > -		}
> > -		return -1;
> > -	default:
> > -		assert(type == FIELD_TYPE_STRING);
> > -		assert(MEM_Str == (MEM_Blob >> 3));
> > -		if ((pMem->flags & MEM_Bool) != 0) {
> > -			const char *str_bool = SQL_TOKEN_BOOLEAN(pMem->u.b);
> > -			if (mem_copy_str0(pMem, str_bool) != 0)
> > -				return -1;
> > -			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);
> > -		pMem->flags &=
> > -			~(MEM_Int | MEM_UInt | MEM_Real | MEM_Blob | MEM_Zero);
> > -		return 0;
> > -	}
> > -}
> 
> It is fascinating how a good code structure allows to get rid of
> all of that old garbage mess almost naturally, and reveals some
> issues.
Absolutly agree! Though I feel that there were found more like a ton of issues.


Diff:


diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index dd979af05..4b619b032 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -517,6 +517,15 @@ int_to_str0(struct Mem *mem)
 	return mem_copy_str0(mem, str);
 }
 
+static inline int
+int_to_bool(struct Mem *mem)
+{
+	mem->u.b = mem->u.i != 0;
+	mem->flags = MEM_Bool;
+	mem->field_type = FIELD_TYPE_BOOLEAN;
+	return 0;
+}
+
 static inline int
 str_to_str0(struct Mem *mem)
 {
@@ -529,6 +538,42 @@ str_to_str0(struct Mem *mem)
 	return 0;
 }
 
+static inline int
+str_to_bin(struct Mem *mem)
+{
+	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
+		     MEM_Blob;
+	mem->field_type = FIELD_TYPE_VARBINARY;
+	return 0;
+}
+
+static inline int
+str_to_bool(struct Mem *mem)
+{
+	char *str = mem->z;
+	bool b;
+	const char *str_true = "TRUE";
+	const char *str_false = "FALSE";
+	uint32_t len_true = strlen(str_true);
+	uint32_t len_false = strlen(str_false);
+
+	for (; str[0] == ' '; str++);
+	if (strncasecmp(str, str_true, len_true) == 0) {
+		b = true;
+		str += len_true;
+	} else if (strncasecmp(str, str_false, len_false) == 0) {
+		b = false;
+		str += len_false;
+	} else {
+		return -1;
+	}
+	for (; str[0] == ' '; str++);
+	if (str[0] != '\0')
+		return -1;
+	mem_set_bool(mem, b);
+	return 0;
+}
+
 static inline int
 bin_to_str(struct Mem *mem)
 {
@@ -564,6 +609,19 @@ bytes_to_int(struct Mem *mem)
 	return 0;
 }
 
+static inline int
+bytes_to_uint(struct Mem *mem)
+{
+	bool is_neg;
+	int64_t i;
+	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
+		return -1;
+	if (is_neg)
+		return -1;
+	mem_set_uint(mem, (uint64_t)i);
+	return 0;
+}
+
 static inline int
 bytes_to_double(struct Mem *mem)
 {
@@ -624,6 +682,15 @@ double_to_str0(struct Mem *mem)
 	return 0;
 }
 
+static inline int
+double_to_bool(struct Mem *mem)
+{
+	mem->u.b = mem->u.r != 0.;
+	mem->flags = MEM_Bool;
+	mem->field_type = FIELD_TYPE_BOOLEAN;
+	return 0;
+}
+
 static inline int
 bool_to_int(struct Mem *mem)
 {
@@ -757,73 +824,6 @@ mem_to_str(struct Mem *mem)
 	return -1;
 }
 
-static inline int
-bytes_to_uint(struct Mem *mem)
-{
-	bool is_neg;
-	int64_t i;
-	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
-		return -1;
-	if (is_neg)
-		return -1;
-	mem_set_uint(mem, (uint64_t)i);
-	return 0;
-}
-
-static inline int
-str_to_bool(struct Mem *mem)
-{
-	char *str = mem->z;
-	bool b;
-	const char *str_true = "TRUE";
-	const char *str_false = "FALSE";
-	uint32_t len_true = strlen(str_true);
-	uint32_t len_false = strlen(str_false);
-
-	for (; str[0] == ' '; str++);
-	if (strncasecmp(str, str_true, len_true) == 0) {
-		b = true;
-		str += len_true;
-	} else if (strncasecmp(str, str_false, len_false) == 0) {
-		b = false;
-		str += len_false;
-	} else {
-		return -1;
-	}
-	for (; str[0] == ' '; str++);
-	if (str[0] != '\0')
-		return -1;
-	mem_set_bool(mem, b);
-	return 0;
-}
-
-static inline int
-int_to_bool(struct Mem *mem)
-{
-	mem->u.b = mem->u.i != 0;
-	mem->flags = MEM_Bool;
-	mem->field_type = FIELD_TYPE_BOOLEAN;
-	return 0;
-}
-
-static inline int
-double_to_bool(struct Mem *mem)
-{
-	mem->u.b = mem->u.r != 0.;
-	mem->flags = MEM_Bool;
-	mem->field_type = FIELD_TYPE_BOOLEAN;
-	return 0;
-}
-
-static inline int
-str_to_bin(struct Mem *mem)
-{
-	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
-		     MEM_Blob;
-	mem->field_type = FIELD_TYPE_VARBINARY;
-	return 0;
-}
-
 int
 mem_cast_explicit(struct Mem *mem, enum field_type type)
 {



New patch:


commit e43b89395710a5beb7e38ef2615856412ddec390
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 12:27:09 2021 +0300

    sql: introduce mem_cast_explicit()
    
    This patch introduces mem_cast_explicit(). This function is used to
    convert a MEM to a given field type according to explicit cast rules.
    
    Part of #5818

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 830fc3840..4b619b032 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -517,6 +517,15 @@ int_to_str0(struct Mem *mem)
 	return mem_copy_str0(mem, str);
 }
 
+static inline int
+int_to_bool(struct Mem *mem)
+{
+	mem->u.b = mem->u.i != 0;
+	mem->flags = MEM_Bool;
+	mem->field_type = FIELD_TYPE_BOOLEAN;
+	return 0;
+}
+
 static inline int
 str_to_str0(struct Mem *mem)
 {
@@ -529,6 +538,42 @@ str_to_str0(struct Mem *mem)
 	return 0;
 }
 
+static inline int
+str_to_bin(struct Mem *mem)
+{
+	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
+		     MEM_Blob;
+	mem->field_type = FIELD_TYPE_VARBINARY;
+	return 0;
+}
+
+static inline int
+str_to_bool(struct Mem *mem)
+{
+	char *str = mem->z;
+	bool b;
+	const char *str_true = "TRUE";
+	const char *str_false = "FALSE";
+	uint32_t len_true = strlen(str_true);
+	uint32_t len_false = strlen(str_false);
+
+	for (; str[0] == ' '; str++);
+	if (strncasecmp(str, str_true, len_true) == 0) {
+		b = true;
+		str += len_true;
+	} else if (strncasecmp(str, str_false, len_false) == 0) {
+		b = false;
+		str += len_false;
+	} else {
+		return -1;
+	}
+	for (; str[0] == ' '; str++);
+	if (str[0] != '\0')
+		return -1;
+	mem_set_bool(mem, b);
+	return 0;
+}
+
 static inline int
 bin_to_str(struct Mem *mem)
 {
@@ -564,6 +609,19 @@ bytes_to_int(struct Mem *mem)
 	return 0;
 }
 
+static inline int
+bytes_to_uint(struct Mem *mem)
+{
+	bool is_neg;
+	int64_t i;
+	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
+		return -1;
+	if (is_neg)
+		return -1;
+	mem_set_uint(mem, (uint64_t)i);
+	return 0;
+}
+
 static inline int
 bytes_to_double(struct Mem *mem)
 {
@@ -624,6 +682,15 @@ double_to_str0(struct Mem *mem)
 	return 0;
 }
 
+static inline int
+double_to_bool(struct Mem *mem)
+{
+	mem->u.b = mem->u.r != 0.;
+	mem->flags = MEM_Bool;
+	mem->field_type = FIELD_TYPE_BOOLEAN;
+	return 0;
+}
+
 static inline int
 bool_to_int(struct Mem *mem)
 {
@@ -757,6 +824,64 @@ mem_to_str(struct Mem *mem)
 	return -1;
 }
 
+int
+mem_cast_explicit(struct Mem *mem, enum field_type type)
+{
+	if ((mem->flags & MEM_Null) != 0) {
+		mem->field_type = type;
+		return 0;
+	}
+	switch (type) {
+	case FIELD_TYPE_UNSIGNED:
+		if ((mem->flags & MEM_UInt) != 0)
+			return 0;
+		if ((mem->flags & MEM_Int) != 0)
+			return -1;
+		if ((mem->flags & MEM_Blob) != 0 &&
+		    (mem->flags & MEM_Subtype) != 0)
+			return -1;
+		if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+			return bytes_to_uint(mem);
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_int(mem);
+		if ((mem->flags & MEM_Bool) != 0)
+			return bool_to_int(mem);
+		return -1;
+	case FIELD_TYPE_STRING:
+		return mem_to_str(mem);
+	case FIELD_TYPE_DOUBLE:
+		return mem_to_double(mem);
+	case FIELD_TYPE_INTEGER:
+		return mem_to_int(mem);
+	case FIELD_TYPE_BOOLEAN:
+		if ((mem->flags & MEM_Bool) != 0)
+			return 0;
+		if ((mem->flags & (MEM_UInt | MEM_Int)) != 0)
+			return int_to_bool(mem);
+		if ((mem->flags & MEM_Str) != 0)
+			return str_to_bool(mem);
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_bool(mem);
+		return -1;
+	case FIELD_TYPE_VARBINARY:
+		if ((mem->flags & MEM_Blob) != 0)
+			return 0;
+		if ((mem->flags & MEM_Str) != 0)
+			return str_to_bin(mem);
+		return -1;
+	case FIELD_TYPE_NUMBER:
+		return mem_to_number(mem);
+	case FIELD_TYPE_SCALAR:
+		if ((mem->flags & MEM_Blob) != 0 &&
+		    (mem->flags & MEM_Subtype) != 0)
+			return -1;
+		return 0;
+	default:
+		break;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -1423,42 +1548,6 @@ valueToText(sql_value * pVal)
 	return pVal->z;
 }
 
-/**
- * According to ANSI SQL string value can be converted to boolean
- * type if string consists of literal "true" or "false" and
- * number of leading and trailing 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[out] 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++);
-	if (strncasecmp(str, SQL_TOKEN_TRUE, strlen(SQL_TOKEN_TRUE)) == 0) {
-		*result = true;
-		str += 4;
-	} else if (strncasecmp(str, SQL_TOKEN_FALSE,
-			       strlen(SQL_TOKEN_FALSE)) == 0) {
-		*result = false;
-		str += 5;
-	} else {
-		return -1;
-	}
-	for (; *str != '\0'; ++str) {
-		if (*str != ' ')
-			return -1;
-	}
-	return 0;
-}
-
 /*
  * Convert a 64-bit IEEE double into a 64-bit signed integer.
  * If the double is out of range of a 64-bit signed integer then
@@ -1794,113 +1883,6 @@ registerTrace(int iReg, Mem *p) {
 }
 #endif
 
-/*
- * Cast the datatype of the value in pMem according to the type
- * @type.  Casting is different from applying type in that a cast
- * is forced.  In other words, the value is converted into the desired
- * type even if that results in loss of data.  This routine is
- * used (for example) to implement the SQL "cast()" operator.
- */
-int
-sqlVdbeMemCast(Mem * pMem, enum field_type type)
-{
-	assert(type < field_type_MAX);
-	if (pMem->flags & MEM_Null)
-		return 0;
-	switch (type) {
-	case FIELD_TYPE_SCALAR:
-		return 0;
-	case FIELD_TYPE_BOOLEAN:
-		if ((pMem->flags & MEM_Int) != 0) {
-			mem_set_bool(pMem, pMem->u.i);
-			return 0;
-		}
-		if ((pMem->flags & MEM_UInt) != 0) {
-			mem_set_bool(pMem, pMem->u.u);
-			return 0;
-		}
-		if ((pMem->flags & MEM_Real) != 0) {
-			mem_set_bool(pMem, pMem->u.r);
-			return 0;
-		}
-		if ((pMem->flags & MEM_Str) != 0) {
-			bool value;
-			if (str_cast_to_boolean(pMem->z, &value) != 0)
-				return -1;
-			mem_set_bool(pMem, value);
-			return 0;
-		}
-		if ((pMem->flags & MEM_Bool) != 0)
-			return 0;
-		return -1;
-	case FIELD_TYPE_INTEGER:
-	case FIELD_TYPE_UNSIGNED:
-		if ((pMem->flags & (MEM_Blob | MEM_Str)) != 0) {
-			bool is_neg;
-			int64_t val;
-			if (sql_atoi64(pMem->z, &val, &is_neg, pMem->n) != 0)
-				return -1;
-			if (type == FIELD_TYPE_UNSIGNED && is_neg)
-				return -1;
-			mem_set_int(pMem, val, is_neg);
-			return 0;
-		}
-		if ((pMem->flags & MEM_Bool) != 0) {
-			pMem->u.u = (uint64_t)pMem->u.b;
-			pMem->flags = MEM_UInt;
-			pMem->field_type = FIELD_TYPE_UNSIGNED;
-			return 0;
-		}
-		if ((pMem->flags & MEM_Real) != 0) {
-			double d = pMem->u.r;
-			if (d < 0. && d >= (double)INT64_MIN) {
-				pMem->u.i = (int64_t)d;
-				pMem->flags = MEM_Int;
-				pMem->field_type = FIELD_TYPE_INTEGER;
-				return 0;
-			}
-			if (d >= 0. && d < (double)UINT64_MAX) {
-				pMem->u.u = (uint64_t)d;
-				pMem->flags = MEM_UInt;
-				pMem->field_type = FIELD_TYPE_UNSIGNED;
-				return 0;
-			}
-			return -1;
-		}
-		if (type == FIELD_TYPE_UNSIGNED &&
-		    (pMem->flags & MEM_UInt) == 0)
-			return -1;
-		return 0;
-	case FIELD_TYPE_DOUBLE:
-		return mem_to_double(pMem);
-	case FIELD_TYPE_NUMBER:
-		return mem_to_number(pMem);
-	case FIELD_TYPE_VARBINARY:
-		if ((pMem->flags & MEM_Blob) != 0)
-			return 0;
-		if ((pMem->flags & MEM_Str) != 0) {
-			MemSetTypeFlag(pMem, MEM_Str);
-			return 0;
-		}
-		return -1;
-	default:
-		assert(type == FIELD_TYPE_STRING);
-		assert(MEM_Str == (MEM_Blob >> 3));
-		if ((pMem->flags & MEM_Bool) != 0) {
-			const char *str_bool = SQL_TOKEN_BOOLEAN(pMem->u.b);
-			if (mem_copy_str0(pMem, str_bool) != 0)
-				return -1;
-			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);
-		pMem->flags &=
-			~(MEM_Int | MEM_UInt | MEM_Real | MEM_Blob | MEM_Zero);
-		return 0;
-	}
-}
-
 /*
  * Make sure the given Mem is \u0000 terminated.
  */
@@ -2058,43 +2040,6 @@ mem_apply_type(struct Mem *record, enum field_type type)
 	}
 }
 
-/**
- * Convert the numeric value contained in MEM to unsigned.
- *
- * @param mem The MEM that contains the numeric value.
- * @retval 0 if the conversion was successful, -1 otherwise.
- */
-static int
-mem_convert_to_unsigned(struct Mem *mem)
-{
-	if ((mem->flags & MEM_UInt) != 0)
-		return 0;
-	if ((mem->flags & MEM_Int) != 0)
-		return -1;
-	if ((mem->flags & MEM_Real) == 0)
-		return -1;
-	double d = mem->u.r;
-	if (d < 0.0 || d >= (double)UINT64_MAX)
-		return -1;
-	mem->u.u = (uint64_t)d;
-	mem->flags = MEM_UInt;
-	mem->field_type = FIELD_TYPE_UNSIGNED;
-	return 0;
-}
-
-int
-mem_convert_to_numeric(struct Mem *mem, enum field_type type)
-{
-	assert(mem_is_num(mem) && sql_type_is_numeric(type));
-	assert(type != FIELD_TYPE_NUMBER);
-	if (type == FIELD_TYPE_DOUBLE)
-		return mem_to_double(mem);
-	if (type == FIELD_TYPE_UNSIGNED)
-		return mem_convert_to_unsigned(mem);
-	assert(type == FIELD_TYPE_INTEGER);
-	return mem_to_int(mem);
-}
-
 static int
 sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 454553d92..7cced5537 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -732,6 +732,10 @@ mem_to_str(struct Mem *mem);
 int
 mem_to_str0(struct Mem *mem);
 
+/** Convert the given MEM to given type according to explicit cast rules. */
+int
+mem_cast_explicit(struct Mem *mem, enum field_type type);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -765,7 +769,6 @@ registerTrace(int iReg, Mem *p);
 #define memIsValid(M)  ((M)->flags & MEM_Undefined)==0
 #endif
 
-int sqlVdbeMemCast(struct Mem *, enum field_type type);
 int sqlVdbeMemNulTerminate(struct Mem *);
 int sqlVdbeMemExpandBlob(struct Mem *);
 #define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlVdbeMemExpandBlob(P):0)
@@ -808,17 +811,6 @@ void sql_value_apply_type(struct Mem *val, enum field_type type);
 int
 mem_apply_type(struct Mem *record, enum field_type type);
 
-/**
- * Convert the numeric value contained in MEM to another numeric
- * type.
- *
- * @param mem The MEM that contains the numeric value.
- * @param type The type to convert to.
- * @retval 0 if the conversion was successful, -1 otherwise.
- */
-int
-mem_convert_to_numeric(struct Mem *mem, enum field_type type);
-
 /** Setters = Change MEM value. */
 
 int sqlVdbeMemClearAndResize(struct Mem * pMem, int n);
@@ -832,12 +824,6 @@ struct Mem *sqlValueNew(struct sql *);
 void
 releaseMemArray(Mem * p, int N);
 
-/*
- * Clear any existing type flags from a Mem and replace them with f
- */
-#define MemSetTypeFlag(p, f) \
-   ((p)->flags = ((p)->flags&~(MEM_TypeMask|MEM_Zero))|f)
-
 /** Getters. */
 
 int
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index de6455c50..049965bd0 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1481,7 +1481,7 @@ case OP_Cast: {                  /* in1 */
 	pIn1 = &aMem[pOp->p1];
 	if (ExpandBlob(pIn1) != 0)
 		goto abort_due_to_error;
-	rc = sqlVdbeMemCast(pIn1, pOp->p2);
+	rc = mem_cast_explicit(pIn1, pOp->p2);
 	/*
 	 * SCALAR is not type itself, but rather an aggregation
 	 * of types. Hence, cast to this type shouldn't change
@@ -2145,7 +2145,7 @@ case OP_ApplyType: {
 			if (!mem_is_num(pIn1))
 				goto type_mismatch;
 			/* Try to convert numeric-to-numeric. */
-			if (mem_convert_to_numeric(pIn1, type) != 0)
+			if (mem_cast_explicit(pIn1, type) != 0)
 				goto type_mismatch;
 		}
 		pIn1++;

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

* Re: [Tarantool-patches] [PATCH v5 44/52] sql: introduce mem_cast_implicit()
  2021-04-13 22:59   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-04-14  0:05     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-14  0:05 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer below. I also included patch since there
were some changes due to conflicts with patches 'sql: introduce mem_cmp_*()
functions' and patch 'sql: introduce mem_ecast_explicit()'.


On Wed, Apr 14, 2021 at 12:59:58AM +0200, Vladislav Shpilevoy wrote:
> Good job on the fixes!
> 
> > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> > index 29634841a..72a84e80d 100644
> > --- a/src/box/sql/vdbe.c
> > +++ b/src/box/sql/vdbe.c
> > @@ -2092,23 +2092,12 @@ case OP_ApplyType: {
> >  	while((type = *(types++)) != field_type_MAX) {
> >  		assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
> >  		assert(memIsValid(pIn1));
> > -		if (!mem_is_type_compatible(pIn1, type)) {
> > -			/* Implicit cast is allowed only to numeric type. */
> > -			if (!sql_type_is_numeric(type))
> > -				goto type_mismatch;
> > -			/* Implicit cast is allowed only from numeric type. */
> > -			if (!mem_is_num(pIn1))
> > -				goto type_mismatch;
> > -			/* Try to convert numeric-to-numeric. */
> > -			if (mem_cast_explicit(pIn1, type) != 0)
> > -				goto type_mismatch;
> > +		if (mem_cast_implicit(pIn1, type) != 0) {
> > +			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
> > +				 mem_str(pIn1), field_type_strs[type]);
> > +			goto abort_due_to_error;
> 
> I think it would be better both for explicit and implicit casts
> to have the diag_set() inside.
I am not sure about this. There are some cases when result of execution is not
checked, which means that diag_set() will be set without actual error. Is this
fine? If so, I have no problem with moving diag_set() to mem_cast_*() functions
and mem_to_*() functions.


New patch:


commit 10dd667c04574a05fe0e75f0d4fd9b0327df76b1
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 12:43:08 2021 +0300

    sql: introduce mem_cast_implicit()
    
    This patch introduces mem_cast_implicit(). This function is used to
    convert a MEM to given type according to implicit cast rules.
    
    Part of #5818

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 4b619b032..537288c14 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -670,6 +670,32 @@ double_to_int_precise(struct Mem *mem)
 	return -1;
 }
 
+static inline int
+double_to_uint(struct Mem *mem)
+{
+	double d = mem->u.r;
+	if (d >= 0 && d < (double)UINT64_MAX) {
+		mem->u.u = (uint64_t)d;
+		mem->flags = MEM_UInt;
+		mem->field_type = FIELD_TYPE_UNSIGNED;
+		return 0;
+	}
+	return -1;
+}
+
+static inline int
+double_to_uint_precise(struct Mem *mem)
+{
+	double d = mem->u.r;
+	if (d >= 0 && d < (double)UINT64_MAX && (double)(uint64_t)d == d) {
+		mem->u.u = (uint64_t)d;
+		mem->flags = MEM_UInt;
+		mem->field_type = FIELD_TYPE_UNSIGNED;
+		return 0;
+	}
+	return -1;
+}
+
 static inline int
 double_to_str0(struct Mem *mem)
 {
@@ -882,6 +908,140 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
 	return -1;
 }
 
+int
+mem_cast_implicit(struct Mem *mem, enum field_type type)
+{
+	if ((mem->flags & MEM_Null) != 0) {
+		mem->field_type = type;
+		return 0;
+	}
+	switch (type) {
+	case FIELD_TYPE_UNSIGNED:
+		if ((mem->flags & MEM_UInt) != 0)
+			return 0;
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_uint(mem);
+		return -1;
+	case FIELD_TYPE_STRING:
+		if ((mem->flags & MEM_Str) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_DOUBLE:
+		if ((mem->flags & MEM_Real) != 0)
+			return 0;
+		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+			return int_to_double(mem);
+		return -1;
+	case FIELD_TYPE_INTEGER:
+		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+			return 0;
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_int(mem);
+		return -1;
+	case FIELD_TYPE_BOOLEAN:
+		if ((mem->flags & MEM_Bool) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_VARBINARY:
+		if ((mem->flags & MEM_Blob) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_NUMBER:
+		if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_MAP:
+		if (mem_is_map(mem))
+			return 0;
+		return -1;
+	case FIELD_TYPE_ARRAY:
+		if (mem_is_array(mem))
+			return 0;
+		return -1;
+	case FIELD_TYPE_SCALAR:
+		if ((mem->flags & MEM_Blob) != 0 &&
+		    (mem->flags & MEM_Subtype) != 0)
+			return -1;
+		return 0;
+	case FIELD_TYPE_ANY:
+		return 0;
+	default:
+		break;
+	}
+	return -1;
+}
+
+int
+mem_cast_implicit_old(struct Mem *mem, enum field_type type)
+{
+	if (mem_is_null(mem))
+		return 0;
+	switch (type) {
+	case FIELD_TYPE_UNSIGNED:
+		if ((mem->flags & MEM_UInt) != 0)
+			return 0;
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_uint_precise(mem);
+		if ((mem->flags & MEM_Str) != 0)
+			return bytes_to_uint(mem);
+		return -1;
+	case FIELD_TYPE_STRING:
+		if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+			return 0;
+		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+			return int_to_str0(mem);
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_str0(mem);
+		return -1;
+	case FIELD_TYPE_DOUBLE:
+		if ((mem->flags & MEM_Real) != 0)
+			return 0;
+		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+			return int_to_double(mem);
+		if ((mem->flags & MEM_Str) != 0)
+			return bin_to_str(mem);
+		return -1;
+	case FIELD_TYPE_INTEGER:
+		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+			return 0;
+		if ((mem->flags & MEM_Str) != 0)
+			return bytes_to_int(mem);
+		if ((mem->flags & MEM_Real) != 0)
+			return double_to_int_precise(mem);
+		return -1;
+	case FIELD_TYPE_BOOLEAN:
+		if ((mem->flags & MEM_Bool) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_VARBINARY:
+		if ((mem->flags & MEM_Blob) != 0)
+			return 0;
+		return -1;
+	case FIELD_TYPE_NUMBER:
+		if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
+			return 0;
+		if ((mem->flags & MEM_Str) != 0)
+			return mem_to_number(mem);
+		return -1;
+	case FIELD_TYPE_MAP:
+		if (mem_is_map(mem))
+			return 0;
+		return -1;
+	case FIELD_TYPE_ARRAY:
+		if (mem_is_array(mem))
+			return 0;
+		return -1;
+	case FIELD_TYPE_SCALAR:
+		if ((mem->flags & MEM_Blob) != 0 &&
+		    (mem->flags & MEM_Subtype) != 0)
+			return -1;
+		return 0;
+	default:
+		break;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -1924,122 +2084,6 @@ sqlVdbeMemExpandBlob(Mem * pMem)
 	return 0;
 }
 
-/*
- * Exported version of mem_apply_type(). This one works on sql_value*,
- * not the internal Mem* type.
- */
-void
-sql_value_apply_type(
-	sql_value *pVal,
-	enum field_type type)
-{
-	mem_apply_type((Mem *) pVal, type);
-}
-
-int
-mem_apply_type(struct Mem *record, enum field_type type)
-{
-	if ((record->flags & MEM_Null) != 0)
-		return 0;
-	assert(type < field_type_MAX);
-	switch (type) {
-	case FIELD_TYPE_INTEGER:
-	case FIELD_TYPE_UNSIGNED:
-		if ((record->flags & (MEM_Bool | MEM_Blob)) != 0)
-			return -1;
-		if ((record->flags & MEM_UInt) == MEM_UInt)
-			return 0;
-		if ((record->flags & MEM_Real) == MEM_Real) {
-			double d = record->u.r;
-			if (d >= 0) {
-				if (double_compare_uint64(d, UINT64_MAX,
-							  1) > 0)
-					return 0;
-				if ((double)(uint64_t)d == d) {
-					record->u.u = (uint64_t)d;
-					record->flags = MEM_UInt;
-					record->field_type =
-						FIELD_TYPE_UNSIGNED;
-				}
-			} else {
-				if (double_compare_nint64(d, INT64_MIN, 1) < 0)
-					return 0;
-				if ((double)(int64_t)d == d) {
-					record->u.i = (int64_t)d;
-					record->flags = MEM_Int;
-					record->field_type = FIELD_TYPE_INTEGER;
-				}
-			}
-			return 0;
-		}
-		if ((record->flags & MEM_Str) != 0) {
-			bool is_neg;
-			int64_t i;
-			if (sql_atoi64(record->z, &i, &is_neg, record->n) != 0)
-				return -1;
-			mem_set_int(record, i, is_neg);
-		}
-		if ((record->flags & MEM_Int) == MEM_Int) {
-			if (type == FIELD_TYPE_UNSIGNED)
-				return -1;
-			return 0;
-		}
-		return 0;
-	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 | MEM_UInt)) != 0)
-			return 0;
-		return mem_to_double(record);
-	case FIELD_TYPE_DOUBLE:
-		if ((record->flags & MEM_Real) != 0)
-			return 0;
-		return mem_to_double(record);
-	case FIELD_TYPE_STRING:
-		/*
-		 * Only attempt the conversion to TEXT if there is
-		 * an integer or real representation (BLOB and
-		 * NULL do not get converted).
-		 */
-		if ((record->flags & MEM_Str) == 0 &&
-		    (record->flags & (MEM_Real | MEM_Int | MEM_UInt)) != 0)
-			mem_to_str(record);
-		record->flags &= ~(MEM_Real | MEM_Int | MEM_UInt);
-		return 0;
-	case FIELD_TYPE_VARBINARY:
-		if ((record->flags & MEM_Blob) == 0)
-			return -1;
-		return 0;
-	case FIELD_TYPE_SCALAR:
-		/* Can't cast MAP and ARRAY to scalar types. */
-		if ((record->flags & MEM_Subtype) != 0 &&
-		    record->subtype == SQL_SUBTYPE_MSGPACK) {
-			assert(mp_typeof(*record->z) == MP_MAP ||
-			       mp_typeof(*record->z) == MP_ARRAY);
-			return -1;
-		}
-		return 0;
-	case FIELD_TYPE_MAP:
-		if ((record->flags & MEM_Subtype) != 0 &&
-		    record->subtype == SQL_SUBTYPE_MSGPACK &&
-		    mp_typeof(*record->z) == MP_MAP)
-			return 0;
-		return -1;
-	case FIELD_TYPE_ARRAY:
-		if ((record->flags & MEM_Subtype) != 0 &&
-		    record->subtype == SQL_SUBTYPE_MSGPACK &&
-		    mp_typeof(*record->z) == MP_ARRAY)
-			return 0;
-		return -1;
-	case FIELD_TYPE_ANY:
-		return 0;
-	default:
-		return -1;
-	}
-}
-
 static int
 sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
 {
@@ -2459,14 +2503,6 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
 	return res;
 }
 
-bool
-mem_is_type_compatible(struct Mem *mem, enum field_type type)
-{
-	enum mp_type mp_type = mem_mp_type(mem);
-	assert(mp_type < MP_EXT);
-	return field_mp_plain_type_is_compatible(type, mp_type, true);
-}
-
 int
 sql_vdbemem_finalize(struct Mem *mem, struct func *func)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 7cced5537..91c1c464f 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -736,6 +736,16 @@ mem_to_str0(struct Mem *mem);
 int
 mem_cast_explicit(struct Mem *mem, enum field_type type);
 
+/** Convert the given MEM to given type according to implicit cast rules. */
+int
+mem_cast_implicit(struct Mem *mem, enum field_type type);
+
+/**
+ * Convert the given MEM to given type according to legacy implicit cast rules.
+ */
+int
+mem_cast_implicit_old(struct Mem *mem, enum field_type type);
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -772,44 +782,6 @@ registerTrace(int iReg, Mem *p);
 int sqlVdbeMemNulTerminate(struct Mem *);
 int sqlVdbeMemExpandBlob(struct Mem *);
 #define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlVdbeMemExpandBlob(P):0)
-void sql_value_apply_type(struct Mem *val, enum field_type type);
-
-
-/**
- * Processing is determined by the field type parameter:
- *
- * INTEGER:
- *    If memory holds floating point value and it can be
- *    converted without loss (2.0 - > 2), it's type is
- *    changed to INT. Otherwise, simply return success status.
- *
- * NUMBER:
- *    If memory holds INT or floating point value,
- *    no actions take place.
- *
- * STRING:
- *    Convert mem to a string representation.
- *
- * SCALAR:
- *    Mem is unchanged, but flag is set to BLOB in case of
- *    scalar-like type. Otherwise, (MAP, ARRAY) conversion
- *    is impossible.
- *
- * BOOLEAN:
- *    If memory holds BOOLEAN no actions take place.
- *
- * ANY:
- *    Mem is unchanged, no actions take place.
- *
- * MAP/ARRAY:
- *    These types can't be casted to scalar ones, or to each
- *    other. So the only valid conversion is to type itself.
- *
- * @param record The value to apply type to.
- * @param type The type to be applied.
- */
-int
-mem_apply_type(struct Mem *record, enum field_type type);
 
 /** Setters = Change MEM value. */
 
@@ -874,17 +846,6 @@ int sqlVdbeMemTooBig(Mem *);
 
 int sqlMemCompare(const Mem *, const Mem *, const struct coll *);
 
-/**
- * Check that MEM_type of the mem is compatible with given type.
- *
- * @param mem The MEM that contains the value to check.
- * @param type The type to check.
- * @retval TRUE if the MEM_type of the value and the given type
- *         are compatible, FALSE otherwise.
- */
-bool
-mem_is_type_compatible(struct Mem *mem, enum field_type type);
-
 /** MEM manipulate functions. */
 
 /**
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 049965bd0..f7b6df0d9 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1645,8 +1645,9 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
 		}
 	} else if (type == FIELD_TYPE_STRING) {
 		if (mem_cmp_str(pIn3, pIn1, &res, pOp->p4.pColl) != 0) {
-			const char *str = mem_apply_type(pIn3, type) != 0 ?
-					  mem_str(pIn3) : mem_str(pIn1);
+			const char *str =
+				mem_cast_implicit_old(pIn3, type) != 0 ?
+				mem_str(pIn3) : mem_str(pIn1);
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
 				 "string");
 			goto abort_due_to_error;
@@ -1655,8 +1656,9 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
 		   mem_is_num(pIn1)) {
 		type = FIELD_TYPE_NUMBER;
 		if (mem_cmp_num(pIn3, pIn1, &res) != 0) {
-			const char *str = mem_apply_type(pIn3, type) != 0 ?
-					  mem_str(pIn3) : mem_str(pIn1);
+			const char *str =
+				mem_cast_implicit_old(pIn3, type) != 0 ?
+				mem_str(pIn3) : mem_str(pIn1);
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
 				 "numeric");
 			goto abort_due_to_error;
@@ -1665,8 +1667,9 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
 		type = FIELD_TYPE_STRING;
 		assert(mem_is_str(pIn3) && mem_is_same_type(pIn3, pIn1));
 		if (mem_cmp_str(pIn3, pIn1, &res, pOp->p4.pColl) != 0) {
-			const char *str = mem_apply_type(pIn3, type) != 0 ?
-					  mem_str(pIn3) : mem_str(pIn1);
+			const char *str =
+				mem_cast_implicit_old(pIn3, type) != 0 ?
+				mem_str(pIn3) : mem_str(pIn1);
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
 				 "string");
 			goto abort_due_to_error;
@@ -2137,23 +2140,12 @@ case OP_ApplyType: {
 	while((type = *(types++)) != field_type_MAX) {
 		assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
 		assert(memIsValid(pIn1));
-		if (!mem_is_type_compatible(pIn1, type)) {
-			/* Implicit cast is allowed only to numeric type. */
-			if (!sql_type_is_numeric(type))
-				goto type_mismatch;
-			/* Implicit cast is allowed only from numeric type. */
-			if (!mem_is_num(pIn1))
-				goto type_mismatch;
-			/* Try to convert numeric-to-numeric. */
-			if (mem_cast_explicit(pIn1, type) != 0)
-				goto type_mismatch;
+		if (mem_cast_implicit(pIn1, type) != 0) {
+			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+				 mem_str(pIn1), field_type_strs[type]);
+			goto abort_due_to_error;
 		}
 		pIn1++;
-		continue;
-type_mismatch:
-		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-			 mem_str(pIn1), field_type_strs[type]);
-		goto abort_due_to_error;
 	}
 	break;
 }
@@ -2212,7 +2204,7 @@ case OP_MakeRecord: {
 	if (types != NULL) {
 		pRec = pData0;
 		do {
-			mem_apply_type(pRec++, *(types++));
+			mem_cast_implicit_old(pRec++, *(types++));
 		} while(types[0] != field_type_MAX);
 	}
 
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 749a3b455..ec413feb8 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -2334,7 +2334,7 @@ sqlVdbeGetBoundValue(Vdbe * v, int iVar, u8 aff)
 			sql_value *pRet = sqlValueNew(v->db);
 			if (pRet) {
 				mem_copy(pRet, pMem);
-				sql_value_apply_type(pRet, aff);
+				mem_cast_implicit_old(pRet, aff);
 			}
 			return pRet;
 		}

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

* Re: [Tarantool-patches] [PATCH v5 45/52] sql: introduce mem_get_int()
  2021-04-13 23:01   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-04-14  0:28     ` Mergen Imeev via Tarantool-patches
  2021-04-14  1:17       ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-14  0:28 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers, diff and new patch below.

On Wed, Apr 14, 2021 at 01:01:02AM +0200, Vladislav Shpilevoy wrote:
> Nice fixes!
> 
> On 09.04.2021 22:53, Mergen Imeev via Tarantool-patches wrote:
> > Thank you for the review! My answers and new patch below.
> > 
> > 
> > On 30.03.2021 02:08, Vladislav Shpilevoy wrote:
> >> Thanks for the patch!
> >>
> >>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> >>> index b644c39d8..0fa0f6ac7 100644
> >>> --- a/src/box/sql/func.c
> >>> +++ b/src/box/sql/func.c
> >>> @@ -1532,10 +1543,11 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
> >>>  static void
> >>>  zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
> >>>  {
> >>> -	i64 n;
> >>> +	int64_t n;
> >>>  	assert(argc == 1);
> >>>  	UNUSED_PARAMETER(argc);
> >>> -	n = sql_value_int64(argv[0]);
> >>> +	bool unused;
> >>> +	mem_get_integer(argv[0], &n, &unused);
> >>
> >> The flag is never used anywhere except one assertion where you can
> >> check the integer value instead. I think you can drop this out
> >> parameter. In future we could add mem_get_int_with_sign() or something
> >> like that if necessary.
> > I think the problem here mostly because most of built-in functions and bitwise
> > operations cannot work with our INTEGER. They can only work with int64. I
> > believe, if we fix this problem, there will be no problems with having this
> > flag.
> 
> My complaint is about the flag. The third argument which is almost never
> used. It makes the code ugly, and does not give a clue it is broken in fact.
> When uint64_t is > INT64_MAX and is returned as int64_t and the flag is
> ignored.
> 
> What about mem_get_int_unsafe()? It would return int64_t truncated like
> before. Return as 'return', not out parameter. Because we also never check
> for fail as I see. And no 'unused' flag. But we would clearly see that these
> places are broken and need attention.
I agree that function with such name would be a good indicator that something
may go wrong here. I created new function, mem_get_int_unfase().


Diff:


diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 701e77d49..0db698174 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -205,9 +205,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 		break;
 	}
 	case MP_INT: {
-		bool unused;
-		int64_t value;
-		mem_get_int(argv[0], &value, &unused);
+		int64_t value = mem_get_int_unsafe(argv[0]);
 		assert(value < 0);
 		sql_result_uint(context, -value);
 		break;
@@ -435,8 +433,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	if (mem_is_null(argv[1]) || (argc == 3 && mem_is_null(argv[2])))
 		return;
 	p0type = sql_value_type(argv[0]);
-	bool unused;
-	mem_get_int(argv[1], &p1, &unused);
+	p1 = mem_get_int_unsafe(argv[1]);
 	if (p0type == MP_BIN) {
 		len = sql_value_bytes(argv[0]);
 		z = sql_value_blob(argv[0]);
@@ -452,7 +449,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
 	}
 	if (argc == 3) {
-		mem_get_int(argv[2], &p2, &unused);
+		p2 = mem_get_int_unsafe(argv[2]);
 		if (p2 < 0) {
 			p2 = -p2;
 			negP2 = 1;
@@ -534,8 +531,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 	if (argc == 2) {
 		if (mem_is_null(argv[1]))
 			return;
-		bool unused;
-		mem_get_int(argv[1], &n, &unused);
+		n = mem_get_int_unsafe(argv[1]);
 		if (n < 0)
 			n = 0;
 	}
@@ -688,8 +684,7 @@ randomBlob(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	bool unused;
-	mem_get_int(argv[0], &n, &unused);
+	n = mem_get_int_unsafe(argv[0]);
 	if (n < 1)
 		return;
 	p = contextMalloc(context, n);
@@ -1233,8 +1228,7 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
 	int64_t n;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	bool unused;
-	mem_get_int(argv[0], &n, &unused);
+	n = mem_get_int_unsafe(argv[0]);
 	if (n < 0)
 		n = 0;
 	if (sql_result_zeroblob64(context, n) != 0) {
@@ -1478,9 +1472,9 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
 	int input_str_sz = sql_value_bytes(arg2);
 	if (sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT) {
 		uint8_t len_one = 1;
-		uint64_t n = sql_value_uint64(arg1);
-		trim_procedure(context, n, (const unsigned char *) " ",
-			       &len_one, 1, input_str, input_str_sz);
+		trim_procedure(context, mem_get_int_unsafe(arg1),
+			       (const unsigned char *) " ", &len_one, 1,
+			       input_str, input_str_sz);
 	} else if ((trim_set = sql_value_text(arg1)) != NULL) {
 		int trim_set_sz = sql_value_bytes(arg1);
 		uint8_t *char_len;
@@ -1518,8 +1512,7 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
 					     &char_len);
 	if (char_cnt == -1)
 		return;
-	uint64_t n = sql_value_uint64(arg1);
-	trim_procedure(context, n, trim_set, char_len,
+	trim_procedure(context, mem_get_int_unsafe(arg1), trim_set, char_len,
 		       char_cnt, input_str, input_str_sz);
 	sql_free(char_len);
 }
@@ -1658,9 +1651,7 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 	}
 	p->cnt++;
 	if (type == MP_INT || type == MP_UINT) {
-		bool unused;
-		int64_t v;
-		mem_get_int(argv[0], &v, &unused);
+		int64_t v = mem_get_int_unsafe(argv[0]);
 		if (type == MP_INT)
 			p->rSum += v;
 		else
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 313ca0ab2..f3d9043e5 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -754,6 +754,20 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type);
 int
 mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg);
 
+/**
+ * Return value of MEM converted to int64_t. This function is not safe, since it
+ * works incorrectly with integer values that are more than INT64_MAX. Also, its
+ * behaviour is undefined if mem_get_int() returned an error.
+ */
+static inline int64_t
+mem_get_int_unsafe(const struct Mem *mem)
+{
+	int64_t i;
+	bool is_neg;
+	mem_get_int(mem, &i, &is_neg);
+	return i;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
index 09da39e81..eb8413f9c 100644
--- a/src/box/sql/printf.c
+++ b/src/box/sql/printf.c
@@ -144,10 +144,7 @@ getIntArg(PrintfArguments * p)
 {
 	if (p->nArg <= p->nUsed)
 		return 0;
-	int64_t i;
-	bool unused;
-	mem_get_int(p->apArg[p->nUsed++], &i, &unused);
-	return (sql_int64)i;
+	return mem_get_int_unsafe(p->apArg[p->nUsed++]);
 }
 
 static double


New patch:


commit 9472eeb564bd0caa039fe49c8fef4c1a7775ce80
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 13:20:37 2021 +0300

    sql: introduce mem_get_int()
    
    This patch introduces mem_get_int() function. This function is used to
    receive integer value from MEM. If value of MEM is not integer, it is
    converted to integer if possible. MEM is not changed.
    
    Part of #5818

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 0282aec74..0db698174 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -205,7 +205,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 		break;
 	}
 	case MP_INT: {
-		int64_t value = sql_value_int64(argv[0]);
+		int64_t value = mem_get_int_unsafe(argv[0]);
 		assert(value < 0);
 		sql_result_uint(context, -value);
 		break;
@@ -421,7 +421,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	const unsigned char *z2;
 	int len;
 	int p0type;
-	i64 p1, p2;
+	int64_t p1, p2;
 	int negP2 = 0;
 
 	if (argc != 2 && argc != 3) {
@@ -433,7 +433,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	if (mem_is_null(argv[1]) || (argc == 3 && mem_is_null(argv[2])))
 		return;
 	p0type = sql_value_type(argv[0]);
-	p1 = sql_value_int(argv[1]);
+	p1 = mem_get_int_unsafe(argv[1]);
 	if (p0type == MP_BIN) {
 		len = sql_value_bytes(argv[0]);
 		z = sql_value_blob(argv[0]);
@@ -449,7 +449,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
 	}
 	if (argc == 3) {
-		p2 = sql_value_int(argv[2]);
+		p2 = mem_get_int_unsafe(argv[2]);
 		if (p2 < 0) {
 			p2 = -p2;
 			negP2 = 1;
@@ -520,7 +520,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 static void
 roundFunc(sql_context * context, int argc, sql_value ** argv)
 {
-	int n = 0;
+	int64_t n = 0;
 	double r;
 	if (argc != 1 && argc != 2) {
 		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROUND",
@@ -531,7 +531,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 	if (argc == 2) {
 		if (mem_is_null(argv[1]))
 			return;
-		n = sql_value_int(argv[1]);
+		n = mem_get_int_unsafe(argv[1]);
 		if (n < 0)
 			n = 0;
 	}
@@ -674,7 +674,7 @@ randomFunc(sql_context * context, int NotUsed, sql_value ** NotUsed2)
 static void
 randomBlob(sql_context * context, int argc, sql_value ** argv)
 {
-	int n;
+	int64_t n;
 	unsigned char *p;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
@@ -684,7 +684,7 @@ randomBlob(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	n = sql_value_int(argv[0]);
+	n = mem_get_int_unsafe(argv[0]);
 	if (n < 1)
 		return;
 	p = contextMalloc(context, n);
@@ -1225,10 +1225,10 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
 static void
 zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
 {
-	i64 n;
+	int64_t n;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	n = sql_value_int64(argv[0]);
+	n = mem_get_int_unsafe(argv[0]);
 	if (n < 0)
 		n = 0;
 	if (sql_result_zeroblob64(context, n) != 0) {
@@ -1472,7 +1472,7 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
 	int input_str_sz = sql_value_bytes(arg2);
 	if (sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT) {
 		uint8_t len_one = 1;
-		trim_procedure(context, sql_value_int(arg1),
+		trim_procedure(context, mem_get_int_unsafe(arg1),
 			       (const unsigned char *) " ", &len_one, 1,
 			       input_str, input_str_sz);
 	} else if ((trim_set = sql_value_text(arg1)) != NULL) {
@@ -1512,7 +1512,7 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
 					     &char_len);
 	if (char_cnt == -1)
 		return;
-	trim_procedure(context, sql_value_int(arg1), trim_set, char_len,
+	trim_procedure(context, mem_get_int_unsafe(arg1), trim_set, char_len,
 		       char_cnt, input_str, input_str_sz);
 	sql_free(char_len);
 }
@@ -1651,7 +1651,7 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 	}
 	p->cnt++;
 	if (type == MP_INT || type == MP_UINT) {
-		int64_t v = sql_value_int64(argv[0]);
+		int64_t v = mem_get_int_unsafe(argv[0]);
 		if (type == MP_INT)
 			p->rSum += v;
 		else
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 537288c14..adf5e236b 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1042,6 +1042,38 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type)
 	return -1;
 }
 
+int
+mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
+{
+	if ((mem->flags & MEM_Int) != 0) {
+		*i = mem->u.i;
+		*is_neg = true;
+		return 0;
+	}
+	if ((mem->flags & MEM_UInt) != 0) {
+		*i = mem->u.i;
+		*is_neg = false;
+		return 0;
+	}
+	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+		return sql_atoi64(mem->z, i, is_neg, mem->n);
+	if ((mem->flags & MEM_Real) != 0) {
+		double d = mem->u.r;
+		if (d < 0 && d >= (double)INT64_MIN) {
+			*i = (int64_t)d;
+			*is_neg = true;
+			return 0;
+		}
+		if (d >= 0 && d < (double)UINT64_MAX) {
+			*i = (int64_t)(uint64_t)d;
+			*is_neg = false;
+			return 0;
+		}
+		return -1;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -1400,12 +1432,12 @@ bitwise_prepare(const struct Mem *left, const struct Mem *right,
 		int64_t *a, int64_t *b)
 {
 	bool unused;
-	if (sqlVdbeIntValue(left, a, &unused) != 0) {
+	if (mem_get_int(left, a, &unused) != 0) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(left),
 			 "integer");
 		return -1;
 	}
-	if (sqlVdbeIntValue(right, b, &unused) != 0) {
+	if (mem_get_int(right, b, &unused) != 0) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(right),
 			 "integer");
 		return -1;
@@ -1494,7 +1526,7 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
 		return 0;
 	int64_t i;
 	bool unused;
-	if (sqlVdbeIntValue(mem, &i, &unused) != 0) {
+	if (mem_get_int(mem, &i, &unused) != 0) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(mem),
 			 "integer");
 		return -1;
@@ -1708,35 +1740,6 @@ valueToText(sql_value * pVal)
 	return pVal->z;
 }
 
-/*
- * Convert a 64-bit IEEE double into a 64-bit signed integer.
- * If the double is out of range of a 64-bit signed integer then
- * return the closest available 64-bit signed integer.
- */
-static int
-doubleToInt64(double r, int64_t *i)
-{
-	/*
-	 * Many compilers we encounter do not define constants for the
-	 * minimum and maximum 64-bit integers, or they define them
-	 * inconsistently.  And many do not understand the "LL" notation.
-	 * So we define our own static constants here using nothing
-	 * larger than a 32-bit integer constant.
-	 */
-	static const int64_t maxInt = LARGEST_INT64;
-	static const int64_t minInt = SMALLEST_INT64;
-	if (r <= (double)minInt) {
-		*i = minInt;
-		return -1;
-	} else if (r >= (double)maxInt) {
-		*i = maxInt;
-		return -1;
-	} else {
-		*i = (int64_t) r;
-		return *i != r;
-	}
-}
-
 /*
  * It is already known that pMem contains an unterminated string.
  * Add the zero terminator.
@@ -2210,42 +2213,6 @@ mem_value_bool(const struct Mem *mem, bool *b)
 	return -1;
 }
 
-/*
- * Return some kind of integer value which is the best we can do
- * at representing the value that *pMem describes as an integer.
- * If pMem is an integer, then the value is exact.  If pMem is
- * a floating-point then the value returned is the integer part.
- * If pMem is a string or blob, then we make an attempt to convert
- * it into an integer and return that.  If pMem represents an
- * an SQL-NULL value, return 0.
- *
- * If pMem represents a string value, its encoding might be changed.
- */
-int
-sqlVdbeIntValue(const struct Mem *pMem, int64_t *i, bool *is_neg)
-{
-	int flags;
-	assert(EIGHT_BYTE_ALIGNMENT(pMem));
-	flags = pMem->flags;
-	if (flags & MEM_Int) {
-		*i = pMem->u.i;
-		*is_neg = true;
-		return 0;
-	} else if (flags & MEM_UInt) {
-		*i = pMem->u.u;
-		*is_neg = false;
-		return 0;
-	} else if (flags & MEM_Real) {
-		*is_neg = pMem->u.r < 0;
-		return doubleToInt64(pMem->u.r, i);
-	} else if (flags & (MEM_Str)) {
-		assert(pMem->z || pMem->n == 0);
-		if (sql_atoi64(pMem->z, i, is_neg, pMem->n) == 0)
-			return 0;
-	}
-	return -1;
-}
-
 /*
  * Return the best representation of pMem that we can get into a
  * double.  If pMem is already a double or an integer, return its
@@ -2316,30 +2283,12 @@ sql_value_boolean(sql_value *val)
 	return b;
 }
 
-int
-sql_value_int(sql_value * pVal)
-{
-	int64_t i = 0;
-	bool is_neg;
-	sqlVdbeIntValue((Mem *) pVal, &i, &is_neg);
-	return (int)i;
-}
-
-sql_int64
-sql_value_int64(sql_value * pVal)
-{
-	int64_t i = 0;
-	bool unused;
-	sqlVdbeIntValue((Mem *) pVal, &i, &unused);
-	return i;
-}
-
 uint64_t
 sql_value_uint64(sql_value *val)
 {
 	int64_t i = 0;
 	bool is_neg;
-	sqlVdbeIntValue((struct Mem *) val, &i, &is_neg);
+	mem_get_int((struct Mem *) val, &i, &is_neg);
 	assert(!is_neg);
 	return i;
 }
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 91c1c464f..f3d9043e5 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -746,6 +746,28 @@ mem_cast_implicit(struct Mem *mem, enum field_type type);
 int
 mem_cast_implicit_old(struct Mem *mem, enum field_type type);
 
+/**
+ * Return value for MEM of INTEGER type. For MEM of all other types convert
+ * value of the MEM to INTEGER if possible and return converted value. Original
+ * MEM is not changed.
+ */
+int
+mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg);
+
+/**
+ * Return value of MEM converted to int64_t. This function is not safe, since it
+ * works incorrectly with integer values that are more than INT64_MAX. Also, its
+ * behaviour is undefined if mem_get_int() returned an error.
+ */
+static inline int64_t
+mem_get_int_unsafe(const struct Mem *mem)
+{
+	int64_t i;
+	bool is_neg;
+	mem_get_int(mem, &i, &is_neg);
+	return i;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -800,7 +822,6 @@ releaseMemArray(Mem * p, int N);
 
 int
 mem_value_bool(const struct Mem *mem, bool *b);
-int sqlVdbeIntValue(const struct Mem *, int64_t *, bool *is_neg);
 int sqlVdbeRealValue(struct Mem *, double *);
 const void *
 sql_value_blob(struct Mem *);
@@ -814,12 +835,6 @@ sql_value_double(struct Mem *);
 bool
 sql_value_boolean(struct Mem *val);
 
-int
-sql_value_int(struct Mem *);
-
-sql_int64
-sql_value_int64(struct Mem *);
-
 uint64_t
 sql_value_uint64(struct Mem *val);
 
diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
index cf32ba3f3..eb8413f9c 100644
--- a/src/box/sql/printf.c
+++ b/src/box/sql/printf.c
@@ -144,7 +144,7 @@ getIntArg(PrintfArguments * p)
 {
 	if (p->nArg <= p->nUsed)
 		return 0;
-	return sql_value_int64(p->apArg[p->nUsed++]);
+	return mem_get_int_unsafe(p->apArg[p->nUsed++]);
 }
 
 static double
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 7a026d21b..0af247ebf 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -445,15 +445,9 @@ sql_column_bytes16(sql_stmt *, int iCol);
 double
 sql_column_double(sql_stmt *, int iCol);
 
-int
-sql_column_int(sql_stmt *, int iCol);
-
 bool
 sql_column_boolean(struct sql_stmt *stmt, int column);
 
-sql_int64
-sql_column_int64(sql_stmt *, int iCol);
-
 uint64_t
 sql_column_uint64(struct sql_stmt *stmt, int column);
 
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index af1174d0a..5e5957496 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -470,24 +470,12 @@ sql_column_double(sql_stmt * pStmt, int i)
 	return sql_value_double(columnMem(pStmt, i));
 }
 
-int
-sql_column_int(sql_stmt * pStmt, int i)
-{
-	return sql_value_int(columnMem(pStmt, i));
-}
-
 bool
 sql_column_boolean(struct sql_stmt *stmt, int i)
 {
 	return sql_value_boolean(columnMem(stmt, i));
 }
 
-sql_int64
-sql_column_int64(sql_stmt * pStmt, int i)
-{
-	return sql_value_int64(columnMem(pStmt, i));
-}
-
 uint64_t
 sql_column_uint64(sql_stmt * pStmt, int i)
 {

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

* Re: [Tarantool-patches] [PATCH v5 46/52] sql: introduce mem_get_uint()
  2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-04-14  0:39     ` Mergen Imeev via Tarantool-patches
  2021-04-14  1:21       ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-14  0:39 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer, diff and new patch below.


On Wed, Apr 14, 2021 at 01:04:05AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> On 09.04.2021 23:08, Mergen Imeev via Tarantool-patches wrote:
> > Thank you for the review! My answer and new patch below.
> > 
> > 
> > On 30.03.2021 02:08, Vladislav Shpilevoy wrote:
> >> Thanks for the patch!
> >>
> >> On 23.03.2021 10:36, Mergen Imeev via Tarantool-patches wrote:
> >>> This patch introduces mem_get_unsigned() function which is used to
> >>> receive unsigned value from MEM.
> >>>
> >>> Part of #5818
> >>> ---
> >>>  src/box/sql/func.c    | 16 +++++++++++-----
> >>>  src/box/sql/mem.c     | 37 +++++++++++++++++++++++++++----------
> >>>  src/box/sql/mem.h     |  6 +++---
> >>>  src/box/sql/sqlInt.h  |  3 ---
> >>>  src/box/sql/vdbeapi.c |  6 ------
> >>>  5 files changed, 41 insertions(+), 27 deletions(-)
> >>>
> >>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> >>> index 0fa0f6ac7..a851d98f2 100644
> >>> --- a/src/box/sql/func.c
> >>> +++ b/src/box/sql/func.c
> >>> @@ -118,9 +118,12 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
> >>>  			luaL_pushint64(L, n);
> >>>  			break;
> >>>  		}
> >>> -		case MP_UINT:
> >>> -			luaL_pushuint64(L, sql_value_uint64(param));
> >>> +		case MP_UINT: {
> >>> +			uint64_t u;
> >>> +			mem_get_unsigned(param, &u);
> >>> +			luaL_pushuint64(L, u);
> >> Maybe we could make 2 functions? One to get the value and ignore
> >> the errors, and the other to get as an out parameter + return an
> >> error?
> >>
> >> For instance, mem_to_uint() - returns uint64_t and internally asserts
> >> that the value is correct. And mem_get_uint() works like your version.
> >>
> >> The same for the other get functions whose result is often ignored.
> > For some functions I created a "proxy" functions in func.c the way you
> > described, but not for this function since it is only used in a few places of
> > sql/func.c. Should I do this for all functions? In func.c I mean. I see this as
> > temporary measure, since I hope we will rework built-in functions one day.
> 
> Unfortunately, 'hope' is not enough. And it is highly possible the code
> will live for long. Therefore I think we need to make it solid where possible
> and clearly state it is unsafe or add assertions where it is not possible.
> 
> Here mem_get_uint() result is ignored always. Even if it fails. I think it
> must be called something like mem_get_uint_unsafe() and return the uint as
> 'return', not via an out argument. Then at least we would see it is broken
> when we are around this code again, and it won't raise questions if it is a
> known issue, and why it is not fixed (this must be in a comment for the
> function).
Understood. I created a new function, mem_get_uint_unsafe().


Diff:


diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 6e6978bbc..5503a9b16 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -201,9 +201,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 	UNUSED_PARAMETER(argc);
 	switch (sql_value_type(argv[0])) {
 	case MP_UINT: {
-		uint64_t u;
-		mem_get_uint(argv[0], &u);
-		sql_result_uint(context, u);
+		sql_result_uint(context, mem_get_uint_unsafe(argv[0]));
 		break;
 	}
 	case MP_INT: {
@@ -1171,7 +1169,7 @@ charFunc(sql_context * context, int argc, sql_value ** argv)
 		if (sql_value_type(argv[i]) == MP_INT)
 			x = 0xfffd;
 		else
-			mem_get_uint(argv[i], &x);
+			x = mem_get_uint_unsafe(argv[i]);
 		if (x > 0x10ffff)
 			x = 0xfffd;
 		c = (unsigned)(x & 0x1fffff);
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 4aadcd3f7..ca8a75b50 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -776,6 +776,18 @@ mem_get_int_unsafe(const struct Mem *mem)
 int
 mem_get_uint(const struct Mem *mem, uint64_t *u);
 
+/**
+ * Return value of MEM converted to uint64_t. This function is not safe, since
+ * its behaviour is undefined if mem_get_uint() returned an error.
+ */
+static inline uint64_t
+mem_get_uint_unsafe(const struct Mem *mem)
+{
+	uint64_t u;
+	mem_get_uint(mem, &u);
+	return u;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.



New patch:


commit b9ca33e93110ecf167329a0f58473371de5c7c45
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 13:55:37 2021 +0300

    sql: introduce mem_get_uint()
    
    This patch introduces mem_get_uint() function. This function is used to
    receive unsigned value from MEM. If value of MEM is not unsigned, it is
    converted to unsigned if possible. MEM is not changed.
    
    Part of #5818

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 0db698174..5503a9b16 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -201,7 +201,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 	UNUSED_PARAMETER(argc);
 	switch (sql_value_type(argv[0])) {
 	case MP_UINT: {
-		sql_result_uint(context, sql_value_uint64(argv[0]));
+		sql_result_uint(context, mem_get_uint_unsafe(argv[0]));
 		break;
 	}
 	case MP_INT: {
@@ -1169,7 +1169,7 @@ charFunc(sql_context * context, int argc, sql_value ** argv)
 		if (sql_value_type(argv[i]) == MP_INT)
 			x = 0xfffd;
 		else
-			x = sql_value_uint64(argv[i]);
+			x = mem_get_uint_unsafe(argv[i]);
 		if (x > 0x10ffff)
 			x = 0xfffd;
 		c = (unsigned)(x & 0x1fffff);
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index adf5e236b..ab31029df 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1074,6 +1074,33 @@ mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
 	return -1;
 }
 
+int
+mem_get_uint(const struct Mem *mem, uint64_t *u)
+{
+	if ((mem->flags & MEM_Int) != 0)
+		return -1;
+	if ((mem->flags & MEM_UInt) != 0) {
+		*u = mem->u.u;
+		return 0;
+	}
+	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
+		bool is_neg;
+		if (sql_atoi64(mem->z, (int64_t *)u, &is_neg, mem->n) != 0 ||
+		    is_neg)
+			return -1;
+		return 0;
+	}
+	if ((mem->flags & MEM_Real) != 0) {
+		double d = mem->u.r;
+		if (d >= 0 && d < (double)UINT64_MAX) {
+			*u = (uint64_t)d;
+			return 0;
+		}
+		return -1;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -2283,16 +2310,6 @@ sql_value_boolean(sql_value *val)
 	return b;
 }
 
-uint64_t
-sql_value_uint64(sql_value *val)
-{
-	int64_t i = 0;
-	bool is_neg;
-	mem_get_int((struct Mem *) val, &i, &is_neg);
-	assert(!is_neg);
-	return i;
-}
-
 const unsigned char *
 sql_value_text(sql_value * pVal)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index f3d9043e5..ca8a75b50 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -768,6 +768,26 @@ mem_get_int_unsafe(const struct Mem *mem)
 	return i;
 }
 
+/**
+ * Return value for MEM of UNSIGNED type. For MEM of all other types convert
+ * value of the MEM to UNSIGNED if possible and return converted value. Original
+ * MEM is not changed.
+ */
+int
+mem_get_uint(const struct Mem *mem, uint64_t *u);
+
+/**
+ * Return value of MEM converted to uint64_t. This function is not safe, since
+ * its behaviour is undefined if mem_get_uint() returned an error.
+ */
+static inline uint64_t
+mem_get_uint_unsafe(const struct Mem *mem)
+{
+	uint64_t u;
+	mem_get_uint(mem, &u);
+	return u;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -835,9 +855,6 @@ sql_value_double(struct Mem *);
 bool
 sql_value_boolean(struct Mem *val);
 
-uint64_t
-sql_value_uint64(struct Mem *val);
-
 const unsigned char *
 sql_value_text(struct Mem *);
 
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 0af247ebf..6ead9b261 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -448,9 +448,6 @@ sql_column_double(sql_stmt *, int iCol);
 bool
 sql_column_boolean(struct sql_stmt *stmt, int column);
 
-uint64_t
-sql_column_uint64(struct sql_stmt *stmt, int column);
-
 const unsigned char *
 sql_column_text(sql_stmt *,
 		    int iCol);
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 5e5957496..1126425bc 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -476,12 +476,6 @@ sql_column_boolean(struct sql_stmt *stmt, int i)
 	return sql_value_boolean(columnMem(stmt, i));
 }
 
-uint64_t
-sql_column_uint64(sql_stmt * pStmt, int i)
-{
-	return sql_value_uint64(columnMem(pStmt, i));
-}
-
 const unsigned char *
 sql_column_text(sql_stmt * pStmt, int i)
 {

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

* Re: [Tarantool-patches] [PATCH v5 47/52] sql: introduce mem_get_double()
  2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-04-14  1:00     ` Mergen Imeev via Tarantool-patches
  2021-04-15  0:17       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-14  1:00 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer, diff and new patch below. New patch also
includes some changes in vdbe.c - previously I missed some code there, which
worked directly with mem->u.r.

On Wed, Apr 14, 2021 at 01:04:36AM +0200, Vladislav Shpilevoy wrote:
> The same as for the previous getters. The result is ignored in 100%
> cases. Therefore it must be called 'unsafe', and return the double
> as 'return'. When fails, it can return 0, so as at least the behaviour
> is not undefined. The same '0 on error' could be used in the other
> getters.
Added mem_get_double_unsafe().


Diff:


diff --git a/src/box/sql/date.c b/src/box/sql/date.c
index dffc23616..dbf460498 100644
--- a/src/box/sql/date.c
+++ b/src/box/sql/date.c
@@ -929,7 +929,7 @@ isDate(sql_context * context, int argc, sql_value ** argv, DateTime * p)
 	}
 	if ((eType = sql_value_type(argv[0])) == MP_DOUBLE
 	    || eType == MP_INT) {
-		setRawDateNumber(p, sql_value_double(argv[0]));
+		setRawDateNumber(p, mem_get_double_unsafe(argv[0]));
 	} else {
 		z = sql_value_text(argv[0]);
 		if (!z || parseDateOrTime(context, (char *)z, p)) {
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 97a2f5bb0..dbf899c02 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -229,9 +229,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 			 * Abs(X) returns 0.0 if X is a string or blob
 			 * that cannot be converted to a numeric value.
 			 */
-			double rVal;
-			if (mem_get_double(argv[0], &rVal) != 0)
-				rVal = 0;
+			double rVal = mem_get_double_unsafe(argv[0]);
 			if (rVal < 0)
 				rVal = -rVal;
 			sql_result_double(context, rVal);
@@ -544,7 +542,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	mem_get_double(argv[0], &r);
+	r = mem_get_double_unsafe(argv[0]);
 	/* If Y==0 and X will fit in a 64-bit int,
 	 * handle the rounding directly,
 	 * otherwise use printf.
@@ -1050,7 +1048,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_DOUBLE:{
 			double r1, r2;
 			char zBuf[50];
-			mem_get_double(argv[0], &r1);
+			r1 = mem_get_double_unsafe(argv[0]);
 			sql_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
 			sqlAtoF(zBuf, &r2, 20);
 			if (r1 != r2) {
@@ -1663,9 +1661,7 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 			p->overflow = 1;
 		}
 	} else {
-		double d;
-		mem_get_double(argv[0], &d);
-		p->rSum += d;
+		p->rSum += mem_get_double_unsafe(argv[0]);
 		p->approx = 1;
 	}
 }
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index d1c3ec386..d7ffa3444 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -796,6 +796,20 @@ mem_get_uint_unsafe(const struct Mem *mem)
 int
 mem_get_double(const struct Mem *mem, double *d);
 
+/**
+ * Return value of MEM converted to double. This function is not safe since
+ * there is no proper processing in case mem_get_double() return an error. In
+ * this case this functions returns 0.
+ */
+static inline double
+mem_get_double_unsafe(const struct Mem *mem)
+{
+	double d;
+	if (mem_get_double(mem, &d) != 0)
+		return 0.;
+	return d;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
index 97df8af35..2f1948ff8 100644
--- a/src/box/sql/printf.c
+++ b/src/box/sql/printf.c
@@ -152,9 +152,7 @@ getDoubleArg(PrintfArguments * p)
 {
 	if (p->nArg <= p->nUsed)
 		return 0.0;
-	double d;
-	mem_get_double(p->apArg[p->nUsed++], &d);
-	return d;
+	return mem_get_double_unsafe(p->apArg[p->nUsed++]);
 }
 
 static char *
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index f7b6df0d9..1deb8e507 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2751,24 +2751,22 @@ case OP_SeekGT: {       /* jump, in3 */
 		if (mem_is_str(pIn3))
 			mem_to_number(pIn3);
 		int64_t i;
-		if (mem_is_uint(pIn3)) {
-			i = pIn3->u.u;
-			is_neg = false;
-		} else if (mem_is_nint(pIn3)) {
-			i = pIn3->u.i;
-			is_neg = true;
-		} else if (mem_is_double(pIn3)) {
-			if (pIn3->u.r > (double)INT64_MAX)
+		if (mem_get_int(pIn3, &i, &is_neg) != 0) {
+			if (!mem_is_double(pIn3)) {
+				diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+					 mem_str(pIn3), "integer");
+				goto abort_due_to_error;
+			}
+			double d = mem_get_double_unsafe(pIn3);
+			assert(d >= (double)INT64_MAX || d < (double)INT64_MIN);
+			/* TODO: add [INT64_MAX, UINT64_MAX) here. */
+			if (d > (double)INT64_MAX)
 				i = INT64_MAX;
-			else if (pIn3->u.r < (double)INT64_MIN)
+			else if (d < (double)INT64_MIN)
 				i = INT64_MIN;
 			else
-				i = pIn3->u.r;
+				i = d;
 			is_neg = i < 0;
-		} else {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 mem_str(pIn3), "integer");
-			goto abort_due_to_error;
 		}
 		iKey = i;
 
@@ -2791,7 +2789,7 @@ case OP_SeekGT: {       /* jump, in3 */
 			 *        (x >  4.9)    ->     (x >= 5)
 			 *        (x <= 4.9)    ->     (x <  5)
 			 */
-			if (pIn3->u.r<(double)iKey) {
+			if (mem_get_double_unsafe(pIn3) < (double)iKey) {
 				assert(OP_SeekGE==(OP_SeekGT-1));
 				assert(OP_SeekLT==(OP_SeekLE-1));
 				assert((OP_SeekLE & 0x0001)==(OP_SeekGT & 0x0001));
@@ -2801,7 +2799,7 @@ case OP_SeekGT: {       /* jump, in3 */
 			/* If the approximation iKey is smaller than the actual real search
 			 * term, substitute <= for < and > for >=.
 			 */
-			else if (pIn3->u.r>(double)iKey) {
+			else if (mem_get_double_unsafe(pIn3) > (double)iKey) {
 				assert(OP_SeekLE==(OP_SeekLT+1));
 				assert(OP_SeekGT==(OP_SeekGE+1));
 				assert((OP_SeekLT & 0x0001)==(OP_SeekGE & 0x0001));



New patch:


commit a007a336371b86c7327db12388f58ee55a511d98
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 14:13:30 2021 +0300

    sql: introduce mem_get_double()
    
    This patch introduces mem_get_double(). This function is used to receive
    double value from MEM. If value of MEM is not double, it is converted to
    double if possible. MEM is not changed.
    
    Part of #5818

diff --git a/src/box/sql/date.c b/src/box/sql/date.c
index dffc23616..dbf460498 100644
--- a/src/box/sql/date.c
+++ b/src/box/sql/date.c
@@ -929,7 +929,7 @@ isDate(sql_context * context, int argc, sql_value ** argv, DateTime * p)
 	}
 	if ((eType = sql_value_type(argv[0])) == MP_DOUBLE
 	    || eType == MP_INT) {
-		setRawDateNumber(p, sql_value_double(argv[0]));
+		setRawDateNumber(p, mem_get_double_unsafe(argv[0]));
 	} else {
 		z = sql_value_text(argv[0]);
 		if (!z || parseDateOrTime(context, (char *)z, p)) {
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 5503a9b16..dbf899c02 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -225,12 +225,11 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 		return;
 	}
 	default:{
-			/* Because sql_value_double() returns 0.0 if the argument is not
-			 * something that can be converted into a number, we have:
-			 * IMP: R-01992-00519 Abs(X) returns 0.0 if X is a string or blob
+			/*
+			 * Abs(X) returns 0.0 if X is a string or blob
 			 * that cannot be converted to a numeric value.
 			 */
-			double rVal = sql_value_double(argv[0]);
+			double rVal = mem_get_double_unsafe(argv[0]);
 			if (rVal < 0)
 				rVal = -rVal;
 			sql_result_double(context, rVal);
@@ -543,7 +542,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	r = sql_value_double(argv[0]);
+	r = mem_get_double_unsafe(argv[0]);
 	/* If Y==0 and X will fit in a 64-bit int,
 	 * handle the rounding directly,
 	 * otherwise use printf.
@@ -1049,7 +1048,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_DOUBLE:{
 			double r1, r2;
 			char zBuf[50];
-			r1 = sql_value_double(argv[0]);
+			r1 = mem_get_double_unsafe(argv[0]);
 			sql_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
 			sqlAtoF(zBuf, &r2, 20);
 			if (r1 != r2) {
@@ -1662,7 +1661,7 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 			p->overflow = 1;
 		}
 	} else {
-		p->rSum += sql_value_double(argv[0]);
+		p->rSum += mem_get_double_unsafe(argv[0]);
 		p->approx = 1;
 	}
 }
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index ab31029df..764735322 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1101,6 +1101,29 @@ mem_get_uint(const struct Mem *mem, uint64_t *u)
 	return -1;
 }
 
+int
+mem_get_double(const struct Mem *mem, double *d)
+{
+	if ((mem->flags & MEM_Real) != 0) {
+		*d = mem->u.r;
+		return 0;
+	}
+	if ((mem->flags & MEM_Int) != 0) {
+		*d = (double)mem->u.i;
+		return 0;
+	}
+	if ((mem->flags & MEM_UInt) != 0) {
+		*d = (double)mem->u.u;
+		return 0;
+	}
+	if ((mem->flags & MEM_Str) != 0) {
+		if (sqlAtoF(mem->z, d, mem->n) == 0)
+			return -1;
+		return 0;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -2240,32 +2263,6 @@ mem_value_bool(const struct Mem *mem, bool *b)
 	return -1;
 }
 
-/*
- * Return the best representation of pMem that we can get into a
- * double.  If pMem is already a double or an integer, return its
- * value.  If it is a string or blob, try to convert it to a double.
- * If it is a NULL, return 0.0.
- */
-int
-sqlVdbeRealValue(Mem * pMem, double *v)
-{
-	assert(EIGHT_BYTE_ALIGNMENT(pMem));
-	if (pMem->flags & MEM_Real) {
-		*v = pMem->u.r;
-		return 0;
-	} else if (pMem->flags & MEM_Int) {
-		*v = (double)pMem->u.i;
-		return 0;
-	} else if ((pMem->flags & MEM_UInt) != 0) {
-		*v = (double)pMem->u.u;
-		return 0;
-	} else if (pMem->flags & MEM_Str) {
-		if (sqlAtoF(pMem->z, v, pMem->n))
-			return 0;
-	}
-	return -1;
-}
-
 /**************************** sql_value_  ******************************
  * The following routines extract information from a Mem or sql_value
  * structure.
@@ -2292,14 +2289,6 @@ sql_value_bytes(sql_value * pVal)
 	return sqlValueBytes(pVal);
 }
 
-double
-sql_value_double(sql_value * pVal)
-{
-	double v = 0.0;
-	sqlVdbeRealValue((Mem *) pVal, &v);
-	return v;
-}
-
 bool
 sql_value_boolean(sql_value *val)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index ca8a75b50..d7ffa3444 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -788,6 +788,28 @@ mem_get_uint_unsafe(const struct Mem *mem)
 	return u;
 }
 
+/**
+ * Return value for MEM of DOUBLE type. For MEM of all other types convert
+ * value of the MEM to DOUBLE if possible and return converted value. Original
+ * MEM is not changed.
+ */
+int
+mem_get_double(const struct Mem *mem, double *d);
+
+/**
+ * Return value of MEM converted to double. This function is not safe since
+ * there is no proper processing in case mem_get_double() return an error. In
+ * this case this functions returns 0.
+ */
+static inline double
+mem_get_double_unsafe(const struct Mem *mem)
+{
+	double d;
+	if (mem_get_double(mem, &d) != 0)
+		return 0.;
+	return d;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -842,16 +864,12 @@ releaseMemArray(Mem * p, int N);
 
 int
 mem_value_bool(const struct Mem *mem, bool *b);
-int sqlVdbeRealValue(struct Mem *, double *);
 const void *
 sql_value_blob(struct Mem *);
 
 int
 sql_value_bytes(struct Mem *);
 
-double
-sql_value_double(struct Mem *);
-
 bool
 sql_value_boolean(struct Mem *val);
 
diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
index eb8413f9c..2f1948ff8 100644
--- a/src/box/sql/printf.c
+++ b/src/box/sql/printf.c
@@ -152,7 +152,7 @@ getDoubleArg(PrintfArguments * p)
 {
 	if (p->nArg <= p->nUsed)
 		return 0.0;
-	return sql_value_double(p->apArg[p->nUsed++]);
+	return mem_get_double_unsafe(p->apArg[p->nUsed++]);
 }
 
 static char *
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 6ead9b261..3f6fa1722 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -442,9 +442,6 @@ sql_column_bytes(sql_stmt *, int iCol);
 int
 sql_column_bytes16(sql_stmt *, int iCol);
 
-double
-sql_column_double(sql_stmt *, int iCol);
-
 bool
 sql_column_boolean(struct sql_stmt *stmt, int column);
 
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index f7b6df0d9..1deb8e507 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2751,24 +2751,22 @@ case OP_SeekGT: {       /* jump, in3 */
 		if (mem_is_str(pIn3))
 			mem_to_number(pIn3);
 		int64_t i;
-		if (mem_is_uint(pIn3)) {
-			i = pIn3->u.u;
-			is_neg = false;
-		} else if (mem_is_nint(pIn3)) {
-			i = pIn3->u.i;
-			is_neg = true;
-		} else if (mem_is_double(pIn3)) {
-			if (pIn3->u.r > (double)INT64_MAX)
+		if (mem_get_int(pIn3, &i, &is_neg) != 0) {
+			if (!mem_is_double(pIn3)) {
+				diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+					 mem_str(pIn3), "integer");
+				goto abort_due_to_error;
+			}
+			double d = mem_get_double_unsafe(pIn3);
+			assert(d >= (double)INT64_MAX || d < (double)INT64_MIN);
+			/* TODO: add [INT64_MAX, UINT64_MAX) here. */
+			if (d > (double)INT64_MAX)
 				i = INT64_MAX;
-			else if (pIn3->u.r < (double)INT64_MIN)
+			else if (d < (double)INT64_MIN)
 				i = INT64_MIN;
 			else
-				i = pIn3->u.r;
+				i = d;
 			is_neg = i < 0;
-		} else {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 mem_str(pIn3), "integer");
-			goto abort_due_to_error;
 		}
 		iKey = i;
 
@@ -2791,7 +2789,7 @@ case OP_SeekGT: {       /* jump, in3 */
 			 *        (x >  4.9)    ->     (x >= 5)
 			 *        (x <= 4.9)    ->     (x <  5)
 			 */
-			if (pIn3->u.r<(double)iKey) {
+			if (mem_get_double_unsafe(pIn3) < (double)iKey) {
 				assert(OP_SeekGE==(OP_SeekGT-1));
 				assert(OP_SeekLT==(OP_SeekLE-1));
 				assert((OP_SeekLE & 0x0001)==(OP_SeekGT & 0x0001));
@@ -2801,7 +2799,7 @@ case OP_SeekGT: {       /* jump, in3 */
 			/* If the approximation iKey is smaller than the actual real search
 			 * term, substitute <= for < and > for >=.
 			 */
-			else if (pIn3->u.r>(double)iKey) {
+			else if (mem_get_double_unsafe(pIn3) > (double)iKey) {
 				assert(OP_SeekLE==(OP_SeekLT+1));
 				assert(OP_SeekGT==(OP_SeekGE+1));
 				assert((OP_SeekLT & 0x0001)==(OP_SeekGE & 0x0001));
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 1126425bc..daa5e0809 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -464,12 +464,6 @@ sql_column_bytes(sql_stmt * pStmt, int i)
 	return sql_value_bytes(columnMem(pStmt, i));
 }
 
-double
-sql_column_double(sql_stmt * pStmt, int i)
-{
-	return sql_value_double(columnMem(pStmt, i));
-}
-
 bool
 sql_column_boolean(struct sql_stmt *stmt, int i)
 {

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

* Re: [Tarantool-patches] [PATCH v5 45/52] sql: introduce mem_get_int()
  2021-04-14  0:28     ` Mergen Imeev via Tarantool-patches
@ 2021-04-14  1:17       ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-14  1:17 UTC (permalink / raw)
  To: Vladislav Shpilevoy, tarantool-patches

I reworker mem_get_int_unsafe() a bit. Now it return 0 in case mem_get_int()
fails. I included only diff.

On Wed, Apr 14, 2021 at 03:28:32AM +0300, Mergen Imeev via Tarantool-patches wrote:
> Thank you for the review! My answers, diff and new patch below.
> 
> On Wed, Apr 14, 2021 at 01:01:02AM +0200, Vladislav Shpilevoy wrote:
> > Nice fixes!
> > 
> > On 09.04.2021 22:53, Mergen Imeev via Tarantool-patches wrote:
> > > Thank you for the review! My answers and new patch below.
> > > 
> > > 
> > > On 30.03.2021 02:08, Vladislav Shpilevoy wrote:
> > >> Thanks for the patch!
> > >>
> > >>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > >>> index b644c39d8..0fa0f6ac7 100644
> > >>> --- a/src/box/sql/func.c
> > >>> +++ b/src/box/sql/func.c
> > >>> @@ -1532,10 +1543,11 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
> > >>>  static void
> > >>>  zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
> > >>>  {
> > >>> -	i64 n;
> > >>> +	int64_t n;
> > >>>  	assert(argc == 1);
> > >>>  	UNUSED_PARAMETER(argc);
> > >>> -	n = sql_value_int64(argv[0]);
> > >>> +	bool unused;
> > >>> +	mem_get_integer(argv[0], &n, &unused);
> > >>
> > >> The flag is never used anywhere except one assertion where you can
> > >> check the integer value instead. I think you can drop this out
> > >> parameter. In future we could add mem_get_int_with_sign() or something
> > >> like that if necessary.
> > > I think the problem here mostly because most of built-in functions and bitwise
> > > operations cannot work with our INTEGER. They can only work with int64. I
> > > believe, if we fix this problem, there will be no problems with having this
> > > flag.
> > 
> > My complaint is about the flag. The third argument which is almost never
> > used. It makes the code ugly, and does not give a clue it is broken in fact.
> > When uint64_t is > INT64_MAX and is returned as int64_t and the flag is
> > ignored.
> > 
> > What about mem_get_int_unsafe()? It would return int64_t truncated like
> > before. Return as 'return', not out parameter. Because we also never check
> > for fail as I see. And no 'unused' flag. But we would clearly see that these
> > places are broken and need attention.
> I agree that function with such name would be a good indicator that something
> may go wrong here. I created new function, mem_get_int_unfase().
> 
> 
> Diff:
> 
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 701e77d49..0db698174 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -205,9 +205,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
>  		break;
>  	}
>  	case MP_INT: {
> -		bool unused;
> -		int64_t value;
> -		mem_get_int(argv[0], &value, &unused);
> +		int64_t value = mem_get_int_unsafe(argv[0]);
>  		assert(value < 0);
>  		sql_result_uint(context, -value);
>  		break;
> @@ -435,8 +433,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
>  	if (mem_is_null(argv[1]) || (argc == 3 && mem_is_null(argv[2])))
>  		return;
>  	p0type = sql_value_type(argv[0]);
> -	bool unused;
> -	mem_get_int(argv[1], &p1, &unused);
> +	p1 = mem_get_int_unsafe(argv[1]);
>  	if (p0type == MP_BIN) {
>  		len = sql_value_bytes(argv[0]);
>  		z = sql_value_blob(argv[0]);
> @@ -452,7 +449,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
>  			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
>  	}
>  	if (argc == 3) {
> -		mem_get_int(argv[2], &p2, &unused);
> +		p2 = mem_get_int_unsafe(argv[2]);
>  		if (p2 < 0) {
>  			p2 = -p2;
>  			negP2 = 1;
> @@ -534,8 +531,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
>  	if (argc == 2) {
>  		if (mem_is_null(argv[1]))
>  			return;
> -		bool unused;
> -		mem_get_int(argv[1], &n, &unused);
> +		n = mem_get_int_unsafe(argv[1]);
>  		if (n < 0)
>  			n = 0;
>  	}
> @@ -688,8 +684,7 @@ randomBlob(sql_context * context, int argc, sql_value ** argv)
>  		context->is_aborted = true;
>  		return;
>  	}
> -	bool unused;
> -	mem_get_int(argv[0], &n, &unused);
> +	n = mem_get_int_unsafe(argv[0]);
>  	if (n < 1)
>  		return;
>  	p = contextMalloc(context, n);
> @@ -1233,8 +1228,7 @@ zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
>  	int64_t n;
>  	assert(argc == 1);
>  	UNUSED_PARAMETER(argc);
> -	bool unused;
> -	mem_get_int(argv[0], &n, &unused);
> +	n = mem_get_int_unsafe(argv[0]);
>  	if (n < 0)
>  		n = 0;
>  	if (sql_result_zeroblob64(context, n) != 0) {
> @@ -1478,9 +1472,9 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
>  	int input_str_sz = sql_value_bytes(arg2);
>  	if (sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT) {
>  		uint8_t len_one = 1;
> -		uint64_t n = sql_value_uint64(arg1);
> -		trim_procedure(context, n, (const unsigned char *) " ",
> -			       &len_one, 1, input_str, input_str_sz);
> +		trim_procedure(context, mem_get_int_unsafe(arg1),
> +			       (const unsigned char *) " ", &len_one, 1,
> +			       input_str, input_str_sz);
>  	} else if ((trim_set = sql_value_text(arg1)) != NULL) {
>  		int trim_set_sz = sql_value_bytes(arg1);
>  		uint8_t *char_len;
> @@ -1518,8 +1512,7 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
>  					     &char_len);
>  	if (char_cnt == -1)
>  		return;
> -	uint64_t n = sql_value_uint64(arg1);
> -	trim_procedure(context, n, trim_set, char_len,
> +	trim_procedure(context, mem_get_int_unsafe(arg1), trim_set, char_len,
>  		       char_cnt, input_str, input_str_sz);
>  	sql_free(char_len);
>  }
> @@ -1658,9 +1651,7 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
>  	}
>  	p->cnt++;
>  	if (type == MP_INT || type == MP_UINT) {
> -		bool unused;
> -		int64_t v;
> -		mem_get_int(argv[0], &v, &unused);
> +		int64_t v = mem_get_int_unsafe(argv[0]);
>  		if (type == MP_INT)
>  			p->rSum += v;
>  		else
> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index 313ca0ab2..f3d9043e5 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -754,6 +754,20 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type);
>  int
>  mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg);
>  
> +/**
> + * Return value of MEM converted to int64_t. This function is not safe, since it
> + * works incorrectly with integer values that are more than INT64_MAX. Also, its
> + * behaviour is undefined if mem_get_int() returned an error.
> + */
> +static inline int64_t
> +mem_get_int_unsafe(const struct Mem *mem)
> +{
> +	int64_t i;
> +	bool is_neg;
> +	mem_get_int(mem, &i, &is_neg);
> +	return i;
> +}
> +
>  /**
>   * Simple type to str convertor. It is used to simplify
>   * error reporting.
> diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
> index 09da39e81..eb8413f9c 100644
> --- a/src/box/sql/printf.c
> +++ b/src/box/sql/printf.c
> @@ -144,10 +144,7 @@ getIntArg(PrintfArguments * p)
>  {
>  	if (p->nArg <= p->nUsed)
>  		return 0;
> -	int64_t i;
> -	bool unused;
> -	mem_get_int(p->apArg[p->nUsed++], &i, &unused);
> -	return (sql_int64)i;
> +	return mem_get_int_unsafe(p->apArg[p->nUsed++]);
>  }
>  
>  static double
> 
> 
> New patch:
> 
> 
> commit 9472eeb564bd0caa039fe49c8fef4c1a7775ce80
> Author: Mergen Imeev <imeevma@gmail.com>
> Date:   Wed Mar 17 13:20:37 2021 +0300
> 
>     sql: introduce mem_get_int()
>     
>     This patch introduces mem_get_int() function. This function is used to
>     receive integer value from MEM. If value of MEM is not integer, it is
>     converted to integer if possible. MEM is not changed.
>     
>     Part of #5818
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 0282aec74..0db698174 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -205,7 +205,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
>  		break;
>  	}
>  	case MP_INT: {
> -		int64_t value = sql_value_int64(argv[0]);
> +		int64_t value = mem_get_int_unsafe(argv[0]);
>  		assert(value < 0);
>  		sql_result_uint(context, -value);
>  		break;
> @@ -421,7 +421,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
>  	const unsigned char *z2;
>  	int len;
>  	int p0type;
> -	i64 p1, p2;
> +	int64_t p1, p2;
>  	int negP2 = 0;
>  
>  	if (argc != 2 && argc != 3) {
> @@ -433,7 +433,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
>  	if (mem_is_null(argv[1]) || (argc == 3 && mem_is_null(argv[2])))
>  		return;
>  	p0type = sql_value_type(argv[0]);
> -	p1 = sql_value_int(argv[1]);
> +	p1 = mem_get_int_unsafe(argv[1]);
>  	if (p0type == MP_BIN) {
>  		len = sql_value_bytes(argv[0]);
>  		z = sql_value_blob(argv[0]);
> @@ -449,7 +449,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
>  			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
>  	}
>  	if (argc == 3) {
> -		p2 = sql_value_int(argv[2]);
> +		p2 = mem_get_int_unsafe(argv[2]);
>  		if (p2 < 0) {
>  			p2 = -p2;
>  			negP2 = 1;
> @@ -520,7 +520,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
>  static void
>  roundFunc(sql_context * context, int argc, sql_value ** argv)
>  {
> -	int n = 0;
> +	int64_t n = 0;
>  	double r;
>  	if (argc != 1 && argc != 2) {
>  		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROUND",
> @@ -531,7 +531,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
>  	if (argc == 2) {
>  		if (mem_is_null(argv[1]))
>  			return;
> -		n = sql_value_int(argv[1]);
> +		n = mem_get_int_unsafe(argv[1]);
>  		if (n < 0)
>  			n = 0;
>  	}
> @@ -674,7 +674,7 @@ randomFunc(sql_context * context, int NotUsed, sql_value ** NotUsed2)
>  static void
>  randomBlob(sql_context * context, int argc, sql_value ** argv)
>  {
> -	int n;
> +	int64_t n;
>  	unsigned char *p;
>  	assert(argc == 1);
>  	UNUSED_PARAMETER(argc);
> @@ -684,7 +684,7 @@ randomBlob(sql_context * context, int argc, sql_value ** argv)
>  		context->is_aborted = true;
>  		return;
>  	}
> -	n = sql_value_int(argv[0]);
> +	n = mem_get_int_unsafe(argv[0]);
>  	if (n < 1)
>  		return;
>  	p = contextMalloc(context, n);
> @@ -1225,10 +1225,10 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
>  static void
>  zeroblobFunc(sql_context * context, int argc, sql_value ** argv)
>  {
> -	i64 n;
> +	int64_t n;
>  	assert(argc == 1);
>  	UNUSED_PARAMETER(argc);
> -	n = sql_value_int64(argv[0]);
> +	n = mem_get_int_unsafe(argv[0]);
>  	if (n < 0)
>  		n = 0;
>  	if (sql_result_zeroblob64(context, n) != 0) {
> @@ -1472,7 +1472,7 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
>  	int input_str_sz = sql_value_bytes(arg2);
>  	if (sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT) {
>  		uint8_t len_one = 1;
> -		trim_procedure(context, sql_value_int(arg1),
> +		trim_procedure(context, mem_get_int_unsafe(arg1),
>  			       (const unsigned char *) " ", &len_one, 1,
>  			       input_str, input_str_sz);
>  	} else if ((trim_set = sql_value_text(arg1)) != NULL) {
> @@ -1512,7 +1512,7 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
>  					     &char_len);
>  	if (char_cnt == -1)
>  		return;
> -	trim_procedure(context, sql_value_int(arg1), trim_set, char_len,
> +	trim_procedure(context, mem_get_int_unsafe(arg1), trim_set, char_len,
>  		       char_cnt, input_str, input_str_sz);
>  	sql_free(char_len);
>  }
> @@ -1651,7 +1651,7 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
>  	}
>  	p->cnt++;
>  	if (type == MP_INT || type == MP_UINT) {
> -		int64_t v = sql_value_int64(argv[0]);
> +		int64_t v = mem_get_int_unsafe(argv[0]);
>  		if (type == MP_INT)
>  			p->rSum += v;
>  		else
> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index 537288c14..adf5e236b 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -1042,6 +1042,38 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type)
>  	return -1;
>  }
>  
> +int
> +mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
> +{
> +	if ((mem->flags & MEM_Int) != 0) {
> +		*i = mem->u.i;
> +		*is_neg = true;
> +		return 0;
> +	}
> +	if ((mem->flags & MEM_UInt) != 0) {
> +		*i = mem->u.i;
> +		*is_neg = false;
> +		return 0;
> +	}
> +	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
> +		return sql_atoi64(mem->z, i, is_neg, mem->n);
> +	if ((mem->flags & MEM_Real) != 0) {
> +		double d = mem->u.r;
> +		if (d < 0 && d >= (double)INT64_MIN) {
> +			*i = (int64_t)d;
> +			*is_neg = true;
> +			return 0;
> +		}
> +		if (d >= 0 && d < (double)UINT64_MAX) {
> +			*i = (int64_t)(uint64_t)d;
> +			*is_neg = false;
> +			return 0;
> +		}
> +		return -1;
> +	}
> +	return -1;
> +}
> +
>  int
>  mem_copy(struct Mem *to, const struct Mem *from)
>  {
> @@ -1400,12 +1432,12 @@ bitwise_prepare(const struct Mem *left, const struct Mem *right,
>  		int64_t *a, int64_t *b)
>  {
>  	bool unused;
> -	if (sqlVdbeIntValue(left, a, &unused) != 0) {
> +	if (mem_get_int(left, a, &unused) != 0) {
>  		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(left),
>  			 "integer");
>  		return -1;
>  	}
> -	if (sqlVdbeIntValue(right, b, &unused) != 0) {
> +	if (mem_get_int(right, b, &unused) != 0) {
>  		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(right),
>  			 "integer");
>  		return -1;
> @@ -1494,7 +1526,7 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
>  		return 0;
>  	int64_t i;
>  	bool unused;
> -	if (sqlVdbeIntValue(mem, &i, &unused) != 0) {
> +	if (mem_get_int(mem, &i, &unused) != 0) {
>  		diag_set(ClientError, ER_SQL_TYPE_MISMATCH, mem_str(mem),
>  			 "integer");
>  		return -1;
> @@ -1708,35 +1740,6 @@ valueToText(sql_value * pVal)
>  	return pVal->z;
>  }
>  
> -/*
> - * Convert a 64-bit IEEE double into a 64-bit signed integer.
> - * If the double is out of range of a 64-bit signed integer then
> - * return the closest available 64-bit signed integer.
> - */
> -static int
> -doubleToInt64(double r, int64_t *i)
> -{
> -	/*
> -	 * Many compilers we encounter do not define constants for the
> -	 * minimum and maximum 64-bit integers, or they define them
> -	 * inconsistently.  And many do not understand the "LL" notation.
> -	 * So we define our own static constants here using nothing
> -	 * larger than a 32-bit integer constant.
> -	 */
> -	static const int64_t maxInt = LARGEST_INT64;
> -	static const int64_t minInt = SMALLEST_INT64;
> -	if (r <= (double)minInt) {
> -		*i = minInt;
> -		return -1;
> -	} else if (r >= (double)maxInt) {
> -		*i = maxInt;
> -		return -1;
> -	} else {
> -		*i = (int64_t) r;
> -		return *i != r;
> -	}
> -}
> -
>  /*
>   * It is already known that pMem contains an unterminated string.
>   * Add the zero terminator.
> @@ -2210,42 +2213,6 @@ mem_value_bool(const struct Mem *mem, bool *b)
>  	return -1;
>  }
>  
> -/*
> - * Return some kind of integer value which is the best we can do
> - * at representing the value that *pMem describes as an integer.
> - * If pMem is an integer, then the value is exact.  If pMem is
> - * a floating-point then the value returned is the integer part.
> - * If pMem is a string or blob, then we make an attempt to convert
> - * it into an integer and return that.  If pMem represents an
> - * an SQL-NULL value, return 0.
> - *
> - * If pMem represents a string value, its encoding might be changed.
> - */
> -int
> -sqlVdbeIntValue(const struct Mem *pMem, int64_t *i, bool *is_neg)
> -{
> -	int flags;
> -	assert(EIGHT_BYTE_ALIGNMENT(pMem));
> -	flags = pMem->flags;
> -	if (flags & MEM_Int) {
> -		*i = pMem->u.i;
> -		*is_neg = true;
> -		return 0;
> -	} else if (flags & MEM_UInt) {
> -		*i = pMem->u.u;
> -		*is_neg = false;
> -		return 0;
> -	} else if (flags & MEM_Real) {
> -		*is_neg = pMem->u.r < 0;
> -		return doubleToInt64(pMem->u.r, i);
> -	} else if (flags & (MEM_Str)) {
> -		assert(pMem->z || pMem->n == 0);
> -		if (sql_atoi64(pMem->z, i, is_neg, pMem->n) == 0)
> -			return 0;
> -	}
> -	return -1;
> -}
> -
>  /*
>   * Return the best representation of pMem that we can get into a
>   * double.  If pMem is already a double or an integer, return its
> @@ -2316,30 +2283,12 @@ sql_value_boolean(sql_value *val)
>  	return b;
>  }
>  
> -int
> -sql_value_int(sql_value * pVal)
> -{
> -	int64_t i = 0;
> -	bool is_neg;
> -	sqlVdbeIntValue((Mem *) pVal, &i, &is_neg);
> -	return (int)i;
> -}
> -
> -sql_int64
> -sql_value_int64(sql_value * pVal)
> -{
> -	int64_t i = 0;
> -	bool unused;
> -	sqlVdbeIntValue((Mem *) pVal, &i, &unused);
> -	return i;
> -}
> -
>  uint64_t
>  sql_value_uint64(sql_value *val)
>  {
>  	int64_t i = 0;
>  	bool is_neg;
> -	sqlVdbeIntValue((struct Mem *) val, &i, &is_neg);
> +	mem_get_int((struct Mem *) val, &i, &is_neg);
>  	assert(!is_neg);
>  	return i;
>  }
> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index 91c1c464f..f3d9043e5 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -746,6 +746,28 @@ mem_cast_implicit(struct Mem *mem, enum field_type type);
>  int
>  mem_cast_implicit_old(struct Mem *mem, enum field_type type);
>  
> +/**
> + * Return value for MEM of INTEGER type. For MEM of all other types convert
> + * value of the MEM to INTEGER if possible and return converted value. Original
> + * MEM is not changed.
> + */
> +int
> +mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg);
> +
> +/**
> + * Return value of MEM converted to int64_t. This function is not safe, since it
> + * works incorrectly with integer values that are more than INT64_MAX. Also, its
> + * behaviour is undefined if mem_get_int() returned an error.
> + */
> +static inline int64_t
> +mem_get_int_unsafe(const struct Mem *mem)
> +{
> +	int64_t i;
> +	bool is_neg;
> +	mem_get_int(mem, &i, &is_neg);
> +	return i;
> +}
> +
>  /**
>   * Simple type to str convertor. It is used to simplify
>   * error reporting.
> @@ -800,7 +822,6 @@ releaseMemArray(Mem * p, int N);
>  
>  int
>  mem_value_bool(const struct Mem *mem, bool *b);
> -int sqlVdbeIntValue(const struct Mem *, int64_t *, bool *is_neg);
>  int sqlVdbeRealValue(struct Mem *, double *);
>  const void *
>  sql_value_blob(struct Mem *);
> @@ -814,12 +835,6 @@ sql_value_double(struct Mem *);
>  bool
>  sql_value_boolean(struct Mem *val);
>  
> -int
> -sql_value_int(struct Mem *);
> -
> -sql_int64
> -sql_value_int64(struct Mem *);
> -
>  uint64_t
>  sql_value_uint64(struct Mem *val);
>  
> diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
> index cf32ba3f3..eb8413f9c 100644
> --- a/src/box/sql/printf.c
> +++ b/src/box/sql/printf.c
> @@ -144,7 +144,7 @@ getIntArg(PrintfArguments * p)
>  {
>  	if (p->nArg <= p->nUsed)
>  		return 0;
> -	return sql_value_int64(p->apArg[p->nUsed++]);
> +	return mem_get_int_unsafe(p->apArg[p->nUsed++]);
>  }
>  
>  static double
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 7a026d21b..0af247ebf 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -445,15 +445,9 @@ sql_column_bytes16(sql_stmt *, int iCol);
>  double
>  sql_column_double(sql_stmt *, int iCol);
>  
> -int
> -sql_column_int(sql_stmt *, int iCol);
> -
>  bool
>  sql_column_boolean(struct sql_stmt *stmt, int column);
>  
> -sql_int64
> -sql_column_int64(sql_stmt *, int iCol);
> -
>  uint64_t
>  sql_column_uint64(struct sql_stmt *stmt, int column);
>  
> diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
> index af1174d0a..5e5957496 100644
> --- a/src/box/sql/vdbeapi.c
> +++ b/src/box/sql/vdbeapi.c
> @@ -470,24 +470,12 @@ sql_column_double(sql_stmt * pStmt, int i)
>  	return sql_value_double(columnMem(pStmt, i));
>  }
>  
> -int
> -sql_column_int(sql_stmt * pStmt, int i)
> -{
> -	return sql_value_int(columnMem(pStmt, i));
> -}
> -
>  bool
>  sql_column_boolean(struct sql_stmt *stmt, int i)
>  {
>  	return sql_value_boolean(columnMem(stmt, i));
>  }
>  
> -sql_int64
> -sql_column_int64(sql_stmt * pStmt, int i)
> -{
> -	return sql_value_int64(columnMem(pStmt, i));
> -}
> -
>  uint64_t
>  sql_column_uint64(sql_stmt * pStmt, int i)
>  {



Diff:


diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index f3d9043e5..8670664c9 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -756,15 +756,16 @@ mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg);
 
 /**
  * Return value of MEM converted to int64_t. This function is not safe, since it
- * works incorrectly with integer values that are more than INT64_MAX. Also, its
- * behaviour is undefined if mem_get_int() returned an error.
+ * returns 0 if mem_get_int() fails. There is no proper handling for this case.
+ * Also it works incorrectly with integer values that are more than INT64_MAX.
  */
 static inline int64_t
 mem_get_int_unsafe(const struct Mem *mem)
 {
 	int64_t i;
 	bool is_neg;
-	mem_get_int(mem, &i, &is_neg);
+	if (mem_get_int(mem, &i, &is_neg) != 0)
+		return 0;
 	return i;
 }
 

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

* Re: [Tarantool-patches] [PATCH v5 46/52] sql: introduce mem_get_uint()
  2021-04-14  0:39     ` Mergen Imeev via Tarantool-patches
@ 2021-04-14  1:21       ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-14  1:21 UTC (permalink / raw)
  To: Vladislav Shpilevoy, tarantool-patches

Now mem_get_uint_unsafe() returns 0 if mem_get_int() fails. Diff below.

On Wed, Apr 14, 2021 at 03:39:48AM +0300, Mergen Imeev via Tarantool-patches wrote:
> Thank you for the review! My answer, diff and new patch below.
> 
> 
> On Wed, Apr 14, 2021 at 01:04:05AM +0200, Vladislav Shpilevoy wrote:
> > Thanks for the fixes!
> > 
> > On 09.04.2021 23:08, Mergen Imeev via Tarantool-patches wrote:
> > > Thank you for the review! My answer and new patch below.
> > > 
> > > 
> > > On 30.03.2021 02:08, Vladislav Shpilevoy wrote:
> > >> Thanks for the patch!
> > >>
> > >> On 23.03.2021 10:36, Mergen Imeev via Tarantool-patches wrote:
> > >>> This patch introduces mem_get_unsigned() function which is used to
> > >>> receive unsigned value from MEM.
> > >>>
> > >>> Part of #5818
> > >>> ---
> > >>>  src/box/sql/func.c    | 16 +++++++++++-----
> > >>>  src/box/sql/mem.c     | 37 +++++++++++++++++++++++++++----------
> > >>>  src/box/sql/mem.h     |  6 +++---
> > >>>  src/box/sql/sqlInt.h  |  3 ---
> > >>>  src/box/sql/vdbeapi.c |  6 ------
> > >>>  5 files changed, 41 insertions(+), 27 deletions(-)
> > >>>
> > >>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > >>> index 0fa0f6ac7..a851d98f2 100644
> > >>> --- a/src/box/sql/func.c
> > >>> +++ b/src/box/sql/func.c
> > >>> @@ -118,9 +118,12 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
> > >>>  			luaL_pushint64(L, n);
> > >>>  			break;
> > >>>  		}
> > >>> -		case MP_UINT:
> > >>> -			luaL_pushuint64(L, sql_value_uint64(param));
> > >>> +		case MP_UINT: {
> > >>> +			uint64_t u;
> > >>> +			mem_get_unsigned(param, &u);
> > >>> +			luaL_pushuint64(L, u);
> > >> Maybe we could make 2 functions? One to get the value and ignore
> > >> the errors, and the other to get as an out parameter + return an
> > >> error?
> > >>
> > >> For instance, mem_to_uint() - returns uint64_t and internally asserts
> > >> that the value is correct. And mem_get_uint() works like your version.
> > >>
> > >> The same for the other get functions whose result is often ignored.
> > > For some functions I created a "proxy" functions in func.c the way you
> > > described, but not for this function since it is only used in a few places of
> > > sql/func.c. Should I do this for all functions? In func.c I mean. I see this as
> > > temporary measure, since I hope we will rework built-in functions one day.
> > 
> > Unfortunately, 'hope' is not enough. And it is highly possible the code
> > will live for long. Therefore I think we need to make it solid where possible
> > and clearly state it is unsafe or add assertions where it is not possible.
> > 
> > Here mem_get_uint() result is ignored always. Even if it fails. I think it
> > must be called something like mem_get_uint_unsafe() and return the uint as
> > 'return', not via an out argument. Then at least we would see it is broken
> > when we are around this code again, and it won't raise questions if it is a
> > known issue, and why it is not fixed (this must be in a comment for the
> > function).
> Understood. I created a new function, mem_get_uint_unsafe().
> 
> 
> Diff:
> 
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 6e6978bbc..5503a9b16 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -201,9 +201,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
>  	UNUSED_PARAMETER(argc);
>  	switch (sql_value_type(argv[0])) {
>  	case MP_UINT: {
> -		uint64_t u;
> -		mem_get_uint(argv[0], &u);
> -		sql_result_uint(context, u);
> +		sql_result_uint(context, mem_get_uint_unsafe(argv[0]));
>  		break;
>  	}
>  	case MP_INT: {
> @@ -1171,7 +1169,7 @@ charFunc(sql_context * context, int argc, sql_value ** argv)
>  		if (sql_value_type(argv[i]) == MP_INT)
>  			x = 0xfffd;
>  		else
> -			mem_get_uint(argv[i], &x);
> +			x = mem_get_uint_unsafe(argv[i]);
>  		if (x > 0x10ffff)
>  			x = 0xfffd;
>  		c = (unsigned)(x & 0x1fffff);
> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index 4aadcd3f7..ca8a75b50 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -776,6 +776,18 @@ mem_get_int_unsafe(const struct Mem *mem)
>  int
>  mem_get_uint(const struct Mem *mem, uint64_t *u);
>  
> +/**
> + * Return value of MEM converted to uint64_t. This function is not safe, since
> + * its behaviour is undefined if mem_get_uint() returned an error.
> + */
> +static inline uint64_t
> +mem_get_uint_unsafe(const struct Mem *mem)
> +{
> +	uint64_t u;
> +	mem_get_uint(mem, &u);
> +	return u;
> +}
> +
>  /**
>   * Simple type to str convertor. It is used to simplify
>   * error reporting.
> 
> 
> 
> New patch:
> 
> 
> commit b9ca33e93110ecf167329a0f58473371de5c7c45
> Author: Mergen Imeev <imeevma@gmail.com>
> Date:   Wed Mar 17 13:55:37 2021 +0300
> 
>     sql: introduce mem_get_uint()
>     
>     This patch introduces mem_get_uint() function. This function is used to
>     receive unsigned value from MEM. If value of MEM is not unsigned, it is
>     converted to unsigned if possible. MEM is not changed.
>     
>     Part of #5818
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 0db698174..5503a9b16 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -201,7 +201,7 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
>  	UNUSED_PARAMETER(argc);
>  	switch (sql_value_type(argv[0])) {
>  	case MP_UINT: {
> -		sql_result_uint(context, sql_value_uint64(argv[0]));
> +		sql_result_uint(context, mem_get_uint_unsafe(argv[0]));
>  		break;
>  	}
>  	case MP_INT: {
> @@ -1169,7 +1169,7 @@ charFunc(sql_context * context, int argc, sql_value ** argv)
>  		if (sql_value_type(argv[i]) == MP_INT)
>  			x = 0xfffd;
>  		else
> -			x = sql_value_uint64(argv[i]);
> +			x = mem_get_uint_unsafe(argv[i]);
>  		if (x > 0x10ffff)
>  			x = 0xfffd;
>  		c = (unsigned)(x & 0x1fffff);
> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index adf5e236b..ab31029df 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -1074,6 +1074,33 @@ mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
>  	return -1;
>  }
>  
> +int
> +mem_get_uint(const struct Mem *mem, uint64_t *u)
> +{
> +	if ((mem->flags & MEM_Int) != 0)
> +		return -1;
> +	if ((mem->flags & MEM_UInt) != 0) {
> +		*u = mem->u.u;
> +		return 0;
> +	}
> +	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
> +		bool is_neg;
> +		if (sql_atoi64(mem->z, (int64_t *)u, &is_neg, mem->n) != 0 ||
> +		    is_neg)
> +			return -1;
> +		return 0;
> +	}
> +	if ((mem->flags & MEM_Real) != 0) {
> +		double d = mem->u.r;
> +		if (d >= 0 && d < (double)UINT64_MAX) {
> +			*u = (uint64_t)d;
> +			return 0;
> +		}
> +		return -1;
> +	}
> +	return -1;
> +}
> +
>  int
>  mem_copy(struct Mem *to, const struct Mem *from)
>  {
> @@ -2283,16 +2310,6 @@ sql_value_boolean(sql_value *val)
>  	return b;
>  }
>  
> -uint64_t
> -sql_value_uint64(sql_value *val)
> -{
> -	int64_t i = 0;
> -	bool is_neg;
> -	mem_get_int((struct Mem *) val, &i, &is_neg);
> -	assert(!is_neg);
> -	return i;
> -}
> -
>  const unsigned char *
>  sql_value_text(sql_value * pVal)
>  {
> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index f3d9043e5..ca8a75b50 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -768,6 +768,26 @@ mem_get_int_unsafe(const struct Mem *mem)
>  	return i;
>  }
>  
> +/**
> + * Return value for MEM of UNSIGNED type. For MEM of all other types convert
> + * value of the MEM to UNSIGNED if possible and return converted value. Original
> + * MEM is not changed.
> + */
> +int
> +mem_get_uint(const struct Mem *mem, uint64_t *u);
> +
> +/**
> + * Return value of MEM converted to uint64_t. This function is not safe, since
> + * its behaviour is undefined if mem_get_uint() returned an error.
> + */
> +static inline uint64_t
> +mem_get_uint_unsafe(const struct Mem *mem)
> +{
> +	uint64_t u;
> +	mem_get_uint(mem, &u);
> +	return u;
> +}
> +
>  /**
>   * Simple type to str convertor. It is used to simplify
>   * error reporting.
> @@ -835,9 +855,6 @@ sql_value_double(struct Mem *);
>  bool
>  sql_value_boolean(struct Mem *val);
>  
> -uint64_t
> -sql_value_uint64(struct Mem *val);
> -
>  const unsigned char *
>  sql_value_text(struct Mem *);
>  
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 0af247ebf..6ead9b261 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -448,9 +448,6 @@ sql_column_double(sql_stmt *, int iCol);
>  bool
>  sql_column_boolean(struct sql_stmt *stmt, int column);
>  
> -uint64_t
> -sql_column_uint64(struct sql_stmt *stmt, int column);
> -
>  const unsigned char *
>  sql_column_text(sql_stmt *,
>  		    int iCol);
> diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
> index 5e5957496..1126425bc 100644
> --- a/src/box/sql/vdbeapi.c
> +++ b/src/box/sql/vdbeapi.c
> @@ -476,12 +476,6 @@ sql_column_boolean(struct sql_stmt *stmt, int i)
>  	return sql_value_boolean(columnMem(stmt, i));
>  }
>  
> -uint64_t
> -sql_column_uint64(sql_stmt * pStmt, int i)
> -{
> -	return sql_value_uint64(columnMem(pStmt, i));
> -}
> -
>  const unsigned char *
>  sql_column_text(sql_stmt * pStmt, int i)
>  {



Diff:


diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index a4121cfac..c2b337414 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -778,14 +778,15 @@ int
 mem_get_uint(const struct Mem *mem, uint64_t *u);
 
 /**
- * Return value of MEM converted to uint64_t. This function is not safe, since
- * its behaviour is undefined if mem_get_uint() returned an error.
+ * Return value of MEM converted to uint64_t. This function is not safe, since it
+ * returns 0 if mem_get_uint() fails. There is no proper handling for this case.
  */
 static inline uint64_t
 mem_get_uint_unsafe(const struct Mem *mem)
 {
 	uint64_t u;
-	mem_get_uint(mem, &u);
+	if (mem_get_uint(mem, &u) != 0)
+		return 0;
 	return u;
 }
 

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

* Re: [Tarantool-patches] [PATCH v5 48/52] sql: introduce mem_get_bool()
  2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-04-14  1:29     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-14  1:29 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer, diff and new patch below.

On Wed, Apr 14, 2021 at 01:04:46AM +0200, Vladislav Shpilevoy wrote:
> The same as for the previous getters.
Added mem_get_bool_unsafe().


Diff:


diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index cd52cb928..3d8028077 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1121,9 +1121,8 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 			break;
 		}
 	case MP_BOOL: {
-		bool b;
-		mem_get_bool(argv[0], &b);
-		sql_result_text(context, SQL_TOKEN_BOOLEAN(b),
+		sql_result_text(context,
+				SQL_TOKEN_BOOLEAN(mem_get_bool_unsafe(argv[0])),
 				-1, SQL_TRANSIENT);
 		break;
 	}
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 4a5b9f4ea..b59197e6c 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -820,6 +820,20 @@ mem_get_double_unsafe(const struct Mem *mem)
 int
 mem_get_bool(const struct Mem *mem, bool *b);
 
+/**
+ * Return value of MEM converted to boolean. This function is not safe since
+ * there is no proper processing in case mem_get_bool() return an error. In
+ * this case this function returns FALSE.
+ */
+static inline bool
+mem_get_bool_unsafe(const struct Mem *mem)
+{
+	bool b;
+	if (mem_get_bool(mem, &b) != 0)
+		return false;
+	return b;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.




New patch:


commit 64a9dfb5535f6f858a0ccf233bc1f63536591d2d
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 14:20:37 2021 +0300

    sql: introduce mem_get_bool()
    
    This patch introduces mem_get_bool(). This function is used to receive
    boolean value from MEM. If value of MEM is not boolean, it is
    converted to boolean if possible. MEM is not changed.
    
    Part of #5818

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index dbf899c02..3d8028077 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1122,7 +1122,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 		}
 	case MP_BOOL: {
 		sql_result_text(context,
-				SQL_TOKEN_BOOLEAN(sql_value_boolean(argv[0])),
+				SQL_TOKEN_BOOLEAN(mem_get_bool_unsafe(argv[0])),
 				-1, SQL_TRANSIENT);
 		break;
 	}
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 764735322..661764f9c 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1124,6 +1124,16 @@ mem_get_double(const struct Mem *mem, double *d)
 	return -1;
 }
 
+int
+mem_get_bool(const struct Mem *mem, bool *b)
+{
+	if ((mem->flags & MEM_Bool) != 0) {
+		*b = mem->u.b;
+		return 0;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -2253,16 +2263,6 @@ releaseMemArray(Mem * p, int N)
 	}
 }
 
-int
-mem_value_bool(const struct Mem *mem, bool *b)
-{
-	if ((mem->flags  & MEM_Bool) != 0) {
-		*b = mem->u.b;
-		return 0;
-	}
-	return -1;
-}
-
 /**************************** sql_value_  ******************************
  * The following routines extract information from a Mem or sql_value
  * structure.
@@ -2289,16 +2289,6 @@ sql_value_bytes(sql_value * pVal)
 	return sqlValueBytes(pVal);
 }
 
-bool
-sql_value_boolean(sql_value *val)
-{
-	bool b = false;
-	int rc = mem_value_bool((struct Mem *) val, &b);
-	assert(rc == 0);
-	(void) rc;
-	return b;
-}
-
 const unsigned char *
 sql_value_text(sql_value * pVal)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index d283aa76a..b59197e6c 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -812,6 +812,28 @@ mem_get_double_unsafe(const struct Mem *mem)
 	return d;
 }
 
+/**
+ * Return value for MEM of BOOLEAN type. For MEM of all other types convert
+ * value of the MEM to BOOLEAN if possible and return converted value. Original
+ * MEM is not changed.
+ */
+int
+mem_get_bool(const struct Mem *mem, bool *b);
+
+/**
+ * Return value of MEM converted to boolean. This function is not safe since
+ * there is no proper processing in case mem_get_bool() return an error. In
+ * this case this function returns FALSE.
+ */
+static inline bool
+mem_get_bool_unsafe(const struct Mem *mem)
+{
+	bool b;
+	if (mem_get_bool(mem, &b) != 0)
+		return false;
+	return b;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -864,17 +886,12 @@ releaseMemArray(Mem * p, int N);
 
 /** Getters. */
 
-int
-mem_value_bool(const struct Mem *mem, bool *b);
 const void *
 sql_value_blob(struct Mem *);
 
 int
 sql_value_bytes(struct Mem *);
 
-bool
-sql_value_boolean(struct Mem *val);
-
 const unsigned char *
 sql_value_text(struct Mem *);
 
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 3f6fa1722..df9469941 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -442,9 +442,6 @@ sql_column_bytes(sql_stmt *, int iCol);
 int
 sql_column_bytes16(sql_stmt *, int iCol);
 
-bool
-sql_column_boolean(struct sql_stmt *stmt, int column);
-
 const unsigned char *
 sql_column_text(sql_stmt *,
 		    int iCol);
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index daa5e0809..c75322899 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -464,12 +464,6 @@ sql_column_bytes(sql_stmt * pStmt, int i)
 	return sql_value_bytes(columnMem(pStmt, i));
 }
 
-bool
-sql_column_boolean(struct sql_stmt *stmt, int i)
-{
-	return sql_value_boolean(columnMem(stmt, i));
-}
-
 const unsigned char *
 sql_column_text(sql_stmt * pStmt, int i)
 {

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

* Re: [Tarantool-patches] [PATCH v5 49/52] sql: introduce mem_get_str0() and mem_as_str0()
  2021-04-13 23:06   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-04-14  1:43     ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-14  1:43 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answers, diff and new patch below.

On Wed, Apr 14, 2021 at 01:06:09AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the discussion!
> 
> See 2 comments below.
> 
> 1. With this getter it is less trivial than with the previous
> ones.
> 
> I just realized, that we call mem_as_str() on function arguments.
> What if a mem is passed to multiple functions, and one of them
> changes it affecting the other functions? What if the same function
> is called multiple times during a query? - the first execution
> affects the argument for the next executions? Does not look right.
> Am I missing something?
> 
> It seems that could be fixed not so hard.
> 
> If the value is already a 0-terminated string, we return it.
> If it is a blob or not terminated string, we extended it, add 0,
> and return. It does not really change the original value anyway.
> Mem.n stays the same, and the first n bytes stay the same.
> If it is a number, we can save its string representation to
> Mem.z, which is not used for numbers.
> 
> Will it work?
> 
Formally it will work, but I am not sure that it is good idea. Still, I believe
that there is no need for this since before this patch such functions as
sql_value_text(), valueToText(), sql_value_bytes() and so on actually converted
MEM to string. So, what we have done is just a bit refactored these functions.
There is no changes in behaviour. So, I expect that there shouldn't be any new
problems. Still, there may be some old problems. Also, our function to get
string or varbinary length actually do not changes MEM as it was before.

> > diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> > index e48e8788c..5848ae729 100644
> > --- a/src/box/sql/mem.h
> > +++ b/src/box/sql/mem.h
> > @@ -568,6 +568,28 @@ mem_get_double(const struct Mem *mem, double *d);
> >  int
> >  mem_get_bool(const struct Mem *mem, bool *b);
> >  
> > +/**
> > + * Return value for MEM of STRING type if MEM contains a NULL-terminated string.
> > + * Otherwise convert value of the MEM to NULL-terminated string if possible and
> > + * return converted value. Original MEM is not changed.
> > + */
> > +int
> > +mem_get_str0(const struct Mem *mem, const char **s);
> > +
> > +/**
> > + * Return value for MEM of STRING type if MEM contains NULL-terminated string.
> > + * Otherwise convert MEM to MEM of string type that contains NULL-terminated
> > + * string and return its value. Return NULL if conversion is impossible.
> > + */
> > +static inline const char *
> > +mem_as_str0(struct Mem *mem)
> > +{
> > + const char *str;
> > + if (mem_to_str0(mem) != 0 || mem_get_str0(mem, &str) != 0)
> 
> 2. If mem_to_str0 succeeded, why can't you return mem->z right away?
> 
True, I can. Fixed.

> > +   return NULL;
> > + return str;
> > +}


Diff:


diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index b16536e6e..8d53fa161 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -850,10 +850,9 @@ mem_get_str0(const struct Mem *mem, const char **s);
 static inline const char *
 mem_as_str0(struct Mem *mem)
 {
-	const char *str;
-	if (mem_to_str0(mem) != 0 || mem_get_str0(mem, &str) != 0)
+	if (mem_to_str0(mem) != 0)
 		return NULL;
-	return str;
+	return mem->z;
 }
 
 /**



New patch:



commit 18a745c61eaeaa2d4b324b5aa13cbe0e80ce9ad7
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Thu Mar 18 12:41:41 2021 +0300

    sql: introduce mem_get_str0() and mem_as_str0()
    
    This patch introduces mem_get_str0() and mem_as_str0(). Function
    mem_get_str0() is used to receive NULL-terminated string from MEM. If
    value of MEM is not NULL-terminated string, it is converted to
    NULL-terminated string if possible. MEM is not changed. Function
    mem_as_str0() is also used to receive NULL-terminated string from MEM,
    however if MEM does not contain NULL-terminated string it converts MEM
    to MEM that contains NULL-terminated string and returns its value.
    
    Part of #5818

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 3d8028077..38a7da69f 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -48,6 +48,12 @@
 #include "box/coll_id_cache.h"
 #include "box/schema.h"
 
+static const unsigned char *
+mem_as_ustr(struct Mem *mem)
+{
+	return (const unsigned char *)mem_as_str0(mem);
+}
+
 /*
  * Return the collating function associated with a function.
  */
@@ -174,7 +180,7 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
 			break;
 		}
 	case MP_STR:{
-			const unsigned char *z = sql_value_text(argv[0]);
+			const unsigned char *z = mem_as_ustr(argv[0]);
 			if (z == 0)
 				return;
 			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
@@ -323,8 +329,8 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
 			 * Character size is equal to
 			 * needle char size.
 			 */
-			haystack_str = sql_value_text(haystack);
-			needle_str = sql_value_text(needle);
+			haystack_str = mem_as_ustr(haystack);
+			needle_str = mem_as_ustr(needle);
 
 			int n_needle_chars =
 				sql_utf8_char_count(needle_str, n_needle_bytes);
@@ -386,8 +392,7 @@ printfFunc(sql_context * context, int argc, sql_value ** argv)
 	int n;
 	sql *db = sql_context_db_handle(context);
 
-	if (argc >= 1
-	    && (zFormat = (const char *)sql_value_text(argv[0])) != 0) {
+	if (argc >= 1 && (zFormat = mem_as_str0(argv[0])) != NULL) {
 		x.nArg = argc - 1;
 		x.nUsed = 0;
 		x.apArg = argv + 1;
@@ -440,7 +445,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 			return;
 		assert(len == sql_value_bytes(argv[0]));
 	} else {
-		z = sql_value_text(argv[0]);
+		z = mem_as_ustr(argv[0]);
 		if (z == 0)
 			return;
 		len = 0;
@@ -602,13 +607,13 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
 		context->is_aborted = true;                                    \
 		return;                                                        \
 	}                                                                      \
-	z2 = (char *)sql_value_text(argv[0]);                              \
+	z2 = mem_as_str0(argv[0]);                                             \
 	n = sql_value_bytes(argv[0]);                                      \
 	/*                                                                     \
 	 * Verify that the call to _bytes()                                    \
 	 * does not invalidate the _text() pointer.                            \
 	 */                                                                    \
-	assert(z2 == (char *)sql_value_text(argv[0]));                     \
+	assert(z2 == mem_as_str0(argv[0]));                                    \
 	if (!z2)                                                               \
 		return;                                                        \
 	z1 = contextMalloc(context, ((i64) n) + 1);                            \
@@ -938,8 +943,8 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		context->is_aborted = true;
 		return;
 	}
-	const char *zB = (const char *) sql_value_text(argv[0]);
-	const char *zA = (const char *) sql_value_text(argv[1]);
+	const char *zB = mem_as_str0(argv[0]);
+	const char *zA = mem_as_str0(argv[1]);
 	const char *zB_end = zB + sql_value_bytes(argv[0]);
 	const char *zA_end = zA + sql_value_bytes(argv[1]);
 
@@ -958,7 +963,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		return;
 	}
 	/* Encoding did not change */
-	assert(zB == (const char *) sql_value_text(argv[0]));
+	assert(zB == mem_as_str0(argv[0]));
 
 	if (argc == 3) {
 		/*
@@ -966,7 +971,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		 * single UTF-8 character. Otherwise, return an
 		 * error.
 		 */
-		const unsigned char *zEsc = sql_value_text(argv[2]);
+		const unsigned char *zEsc = mem_as_ustr(argv[2]);
 		if (zEsc == 0)
 			return;
 		if (sql_utf8_char_count(zEsc, sql_value_bytes(argv[2])) != 1) {
@@ -1095,7 +1100,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_STR:{
 			int i, j;
 			u64 n;
-			const unsigned char *zArg = sql_value_text(argv[0]);
+			const unsigned char *zArg = mem_as_ustr(argv[0]);
 			char *z;
 
 			if (zArg == 0)
@@ -1141,7 +1146,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 static void
 unicodeFunc(sql_context * context, int argc, sql_value ** argv)
 {
-	const unsigned char *z = sql_value_text(argv[0]);
+	const unsigned char *z = mem_as_ustr(argv[0]);
 	(void)argc;
 	if (z && z[0])
 		sql_result_uint(context, sqlUtf8Read(&z));
@@ -1259,12 +1264,12 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 
 	assert(argc == 3);
 	UNUSED_PARAMETER(argc);
-	zStr = sql_value_text(argv[0]);
+	zStr = mem_as_ustr(argv[0]);
 	if (zStr == 0)
 		return;
 	nStr = sql_value_bytes(argv[0]);
-	assert(zStr == sql_value_text(argv[0]));	/* No encoding change */
-	zPattern = sql_value_text(argv[1]);
+	assert(zStr == mem_as_ustr(argv[0]));	/* No encoding change */
+	zPattern = mem_as_ustr(argv[1]);
 	if (zPattern == 0) {
 		assert(mem_is_null(argv[1])
 		       || sql_context_db_handle(context)->mallocFailed);
@@ -1276,12 +1281,12 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 		sql_result_value(context, argv[0]);
 		return;
 	}
-	assert(zPattern == sql_value_text(argv[1]));	/* No encoding change */
-	zRep = sql_value_text(argv[2]);
+	assert(zPattern == mem_as_ustr(argv[1]));	/* No encoding change */
+	zRep = mem_as_ustr(argv[2]);
 	if (zRep == 0)
 		return;
 	nRep = sql_value_bytes(argv[2]);
-	assert(zRep == sql_value_text(argv[2]));
+	assert(zRep == mem_as_ustr(argv[2]));
 	nOut = nStr + 1;
 	assert(nOut < SQL_MAX_LENGTH);
 	zOut = contextMalloc(context, (i64) nOut);
@@ -1443,7 +1448,7 @@ trim_func_one_arg(struct sql_context *context, sql_value *arg)
 	else
 		default_trim = (const unsigned char *) " ";
 	int input_str_sz = sql_value_bytes(arg);
-	const unsigned char *input_str = sql_value_text(arg);
+	const unsigned char *input_str = mem_as_ustr(arg);
 	uint8_t trim_char_len[1] = { 1 };
 	trim_procedure(context, TRIM_BOTH, default_trim, trim_char_len, 1,
 		       input_str, input_str_sz);
@@ -1465,7 +1470,7 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
 		   sql_value *arg2)
 {
 	const unsigned char *input_str, *trim_set;
-	if ((input_str = sql_value_text(arg2)) == NULL)
+	if ((input_str = mem_as_ustr(arg2)) == NULL)
 		return;
 
 	int input_str_sz = sql_value_bytes(arg2);
@@ -1474,7 +1479,7 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
 		trim_procedure(context, mem_get_int_unsafe(arg1),
 			       (const unsigned char *) " ", &len_one, 1,
 			       input_str, input_str_sz);
-	} else if ((trim_set = sql_value_text(arg1)) != NULL) {
+	} else if ((trim_set = mem_as_ustr(arg1)) != NULL) {
 		int trim_set_sz = sql_value_bytes(arg1);
 		uint8_t *char_len;
 		int char_cnt = trim_prepare_char_len(context, trim_set,
@@ -1500,8 +1505,8 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
 {
 	assert(sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT);
 	const unsigned char *input_str, *trim_set;
-	if ((input_str = sql_value_text(arg3)) == NULL ||
-	    (trim_set = sql_value_text(arg2)) == NULL)
+	if ((input_str = mem_as_ustr(arg3)) == NULL ||
+	    (trim_set = mem_as_ustr(arg2)) == NULL)
 		return;
 
 	int trim_set_sz = sql_value_bytes(arg2);
@@ -1573,7 +1578,7 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	zIn = (u8 *) sql_value_text(argv[0]);
+	zIn = (u8 *) mem_as_ustr(argv[0]);
 	if (zIn == 0)
 		zIn = (u8 *) "";
 	for (i = 0; zIn[i] && !sqlIsalpha(zIn[i]); i++) {
@@ -1818,7 +1823,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 		pAccum->mxAlloc = db->aLimit[SQL_LIMIT_LENGTH];
 		if (!firstTerm) {
 			if (argc == 2) {
-				zSep = (char *)sql_value_text(argv[1]);
+				zSep = mem_as_str0(argv[1]);
 				nSep = sql_value_bytes(argv[1]);
 			} else {
 				zSep = ",";
@@ -1827,7 +1832,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 			if (zSep)
 				sqlStrAccumAppend(pAccum, zSep, nSep);
 		}
-		zVal = (char *)sql_value_text(argv[0]);
+		zVal = mem_as_str0(argv[0]);
 		nVal = sql_value_bytes(argv[0]);
 		if (zVal)
 			sqlStrAccumAppend(pAccum, zVal, nVal);
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 661764f9c..c73edb21e 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1134,6 +1134,15 @@ mem_get_bool(const struct Mem *mem, bool *b)
 	return -1;
 }
 
+int
+mem_get_str0(const struct Mem *mem, const char **s)
+{
+	if ((mem->flags & MEM_Str) == 0 || (mem->flags & MEM_Term) == 0)
+		return -1;
+	*s = mem->z;
+	return 0;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -1777,45 +1786,6 @@ mem_has_msgpack_subtype(struct Mem *mem)
 	       mem->subtype == SQL_SUBTYPE_MSGPACK;
 }
 
-/*
- * The pVal argument is known to be a value other than NULL.
- * Convert it into a string with encoding enc and return a pointer
- * to a zero-terminated version of that string.
- */
-static SQL_NOINLINE const void *
-valueToText(sql_value * pVal)
-{
-	assert(pVal != 0);
-	assert((pVal->flags & (MEM_Null)) == 0);
-	if ((pVal->flags & (MEM_Blob | MEM_Str)) &&
-	    !mem_has_msgpack_subtype(pVal)) {
-		if (ExpandBlob(pVal))
-			return 0;
-		pVal->flags |= MEM_Str;
-		sqlVdbeMemNulTerminate(pVal);	/* IMP: R-31275-44060 */
-	} else {
-		mem_to_str(pVal);
-		assert(0 == (1 & SQL_PTR_TO_INT(pVal->z)));
-	}
-	return pVal->z;
-}
-
-/*
- * It is already known that pMem contains an unterminated string.
- * Add the zero terminator.
- */
-static SQL_NOINLINE int
-vdbeMemAddTerminator(Mem * pMem)
-{
-	if (sqlVdbeMemGrow(pMem, pMem->n + 2, 1)) {
-		return -1;
-	}
-	pMem->z[pMem->n] = 0;
-	pMem->z[pMem->n + 1] = 0;
-	pMem->flags |= MEM_Term;
-	return 0;
-}
-
 /*
  * Both *pMem1 and *pMem2 contain string values. Compare the two values
  * using the collation sequence pColl. As usual, return a negative , zero
@@ -1917,7 +1887,9 @@ sql_value_type(sql_value *pVal)
 static SQL_NOINLINE int
 valueBytes(sql_value * pVal)
 {
-	return valueToText(pVal) != 0 ? pVal->n : 0;
+	if (mem_to_str(pVal) != 0)
+		return 0;
+	return pVal->n;
 }
 
 int
@@ -2106,21 +2078,6 @@ registerTrace(int iReg, Mem *p) {
 }
 #endif
 
-/*
- * Make sure the given Mem is \u0000 terminated.
- */
-int
-sqlVdbeMemNulTerminate(Mem * pMem)
-{
-	testcase((pMem->flags & (MEM_Term | MEM_Str)) == (MEM_Term | MEM_Str));
-	testcase((pMem->flags & (MEM_Term | MEM_Str)) == 0);
-	if ((pMem->flags & (MEM_Term | MEM_Str)) != MEM_Str) {
-		return 0;	/* Nothing to do */
-	} else {
-		return vdbeMemAddTerminator(pMem);
-	}
-}
-
 /*
  * If the given Mem* has a zero-filled tail, turn it into an ordinary
  * blob stored in dynamically allocated space.
@@ -2279,7 +2236,9 @@ sql_value_blob(sql_value * pVal)
 		p->flags |= MEM_Blob;
 		return p->n ? p->z : 0;
 	} else {
-		return sql_value_text(pVal);
+		if (mem_to_str(pVal) != 0)
+			return NULL;
+		return pVal->z;
 	}
 }
 
@@ -2289,36 +2248,6 @@ sql_value_bytes(sql_value * pVal)
 	return sqlValueBytes(pVal);
 }
 
-const unsigned char *
-sql_value_text(sql_value * pVal)
-{
-	return (const unsigned char *)sqlValueText(pVal);
-}
-
-/* This function is only available internally, it is not part of the
- * external API. It works in a similar way to sql_value_text(),
- * except the data returned is in the encoding specified by the second
- * parameter, which must be one of SQL_UTF16BE, SQL_UTF16LE or
- * SQL_UTF8.
- *
- * (2006-02-16:)  The enc value can be or-ed with SQL_UTF16_ALIGNED.
- * If that is the case, then the result must be aligned on an even byte
- * boundary.
- */
-const void *
-sqlValueText(sql_value * pVal)
-{
-	if (!pVal)
-		return 0;
-	if ((pVal->flags & (MEM_Str | MEM_Term)) == (MEM_Str | MEM_Term)) {
-		return pVal->z;
-	}
-	if (pVal->flags & MEM_Null) {
-		return 0;
-	}
-	return valueToText(pVal);
-}
-
 /*
  * Return a pointer to static memory containing an SQL NULL value.
  */
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index b59197e6c..8d53fa161 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -834,6 +834,27 @@ mem_get_bool_unsafe(const struct Mem *mem)
 	return b;
 }
 
+/**
+ * Return value for MEM of STRING type if MEM contains a NULL-terminated string.
+ * Otherwise convert value of the MEM to NULL-terminated string if possible and
+ * return converted value. Original MEM is not changed.
+ */
+int
+mem_get_str0(const struct Mem *mem, const char **s);
+
+/**
+ * Return value for MEM of STRING type if MEM contains NULL-terminated string.
+ * Otherwise convert MEM to MEM of string type that contains NULL-terminated
+ * string and return its value. Return NULL if conversion is impossible.
+ */
+static inline const char *
+mem_as_str0(struct Mem *mem)
+{
+	if (mem_to_str0(mem) != 0)
+		return NULL;
+	return mem->z;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -867,7 +888,6 @@ registerTrace(int iReg, Mem *p);
 #define memIsValid(M)  ((M)->flags & MEM_Undefined)==0
 #endif
 
-int sqlVdbeMemNulTerminate(struct Mem *);
 int sqlVdbeMemExpandBlob(struct Mem *);
 #define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlVdbeMemExpandBlob(P):0)
 
@@ -892,11 +912,6 @@ sql_value_blob(struct Mem *);
 int
 sql_value_bytes(struct Mem *);
 
-const unsigned char *
-sql_value_text(struct Mem *);
-
-const void *sqlValueText(struct Mem *);
-
 #define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))])
 
 const Mem *
diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
index 2f1948ff8..b4ab0d0f9 100644
--- a/src/box/sql/printf.c
+++ b/src/box/sql/printf.c
@@ -160,7 +160,8 @@ getTextArg(PrintfArguments * p)
 {
 	if (p->nArg <= p->nUsed)
 		return 0;
-	return (char *)sql_value_text(p->apArg[p->nUsed++]);
+	struct Mem *mem = p->apArg[p->nUsed++];
+	return (char *)mem_as_str0(mem);
 }
 
 /*
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index df9469941..51613aea3 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -442,10 +442,6 @@ sql_column_bytes(sql_stmt *, int iCol);
 int
 sql_column_bytes16(sql_stmt *, int iCol);
 
-const unsigned char *
-sql_column_text(sql_stmt *,
-		    int iCol);
-
 char *
 sql_result_to_msgpack(struct sql_stmt *stmt, uint32_t *tuple_size,
 		      struct region *region);
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index c75322899..da161e298 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -464,12 +464,6 @@ sql_column_bytes(sql_stmt * pStmt, int i)
 	return sql_value_bytes(columnMem(pStmt, i));
 }
 
-const unsigned char *
-sql_column_text(sql_stmt * pStmt, int i)
-{
-	return sql_value_text(columnMem(pStmt, i));
-}
-
 char *
 sql_result_to_msgpack(struct sql_stmt *stmt, uint32_t *tuple_size,
 		      struct region *region)
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index 93de722cb..fe7329ea8 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -312,8 +312,10 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
 		pVal =
 		    sqlVdbeGetBoundValue(pReprepare, iCol,
 					     FIELD_TYPE_SCALAR);
-		if (pVal != NULL && mem_is_str(pVal))
-			z = (char *)sql_value_text(pVal);
+		if (pVal != NULL && mem_is_str(pVal)) {
+			if (mem_as_str0(pVal) == NULL)
+				return -1;
+		}
 		assert(pRight->op == TK_VARIABLE || pRight->op == TK_REGISTER);
 	} else if (op == TK_STRING) {
 		z = pRight->u.zToken;

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

* Re: [Tarantool-patches] [PATCH v5 51/52] sql: introduce mem_get_bytes_len()
  2021-04-13 23:06   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-04-14  1:55     ` Mergen Imeev via Tarantool-patches
  2021-04-15  0:21       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-14  1:55 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer, diff and new patch below.

On Wed, Apr 14, 2021 at 01:06:53AM +0200, Vladislav Shpilevoy wrote:
> Good job on the patch!
> 
> > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > index 2896a5c31..746bda0f4 100644
> > --- a/src/box/sql/func.c
> > +++ b/src/box/sql/func.c
> > @@ -65,6 +65,14 @@ mem_as_bin(struct Mem *mem)
> >  	return s;
> >  }
> >  
> > +static int
> > +mem_get_length(struct Mem *mem)
> > +{
> > +	uint32_t len;
> > +	mem_get_bytes_len(mem, &len);
> 
> mem_get_bytes_len() is never used except here. I suggest you to
> rename it to mem_len(), drop mem_get_length(), and use mem_len()
> everywhere.
> 
> Since you ignore the error always, it probably must has 'unsafe'
> suffix to emphasize it is broken.
I added new function mem_len_unsafe(). I decided to left mem_get_bytes_len()
since I think that there should be safe version of the function. In this case
we do not have to write a new function when we need it.


Diff:


diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 0ea46f01c..9c28d5122 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -65,14 +65,6 @@ mem_as_bin(struct Mem *mem)
 	return s;
 }
 
-static int
-mem_get_length(struct Mem *mem)
-{
-	uint32_t len;
-	mem_get_bytes_len(mem, &len);
-	return len;
-}
-
 /*
  * Return the collating function associated with a function.
  */
@@ -196,14 +188,14 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_BOOL:
 	case MP_DOUBLE:{
 			mem_as_bin(argv[0]);
-			sql_result_uint(context, mem_get_length(argv[0]));
+			sql_result_uint(context, mem_len_unsafe(argv[0]));
 			break;
 		}
 	case MP_STR:{
 			const unsigned char *z = mem_as_ustr(argv[0]);
 			if (z == 0)
 				return;
-			len = sql_utf8_char_count(z, mem_get_length(argv[0]));
+			len = sql_utf8_char_count(z, mem_len_unsafe(argv[0]));
 			sql_result_uint(context, len);
 			break;
 		}
@@ -313,8 +305,8 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
 		return;
 	}
 
-	int n_needle_bytes = mem_get_length(needle);
-	int n_haystack_bytes = mem_get_length(haystack);
+	int n_needle_bytes = mem_len_unsafe(needle);
+	int n_haystack_bytes = mem_len_unsafe(haystack);
 	int position = 1;
 	if (n_needle_bytes > 0) {
 		const unsigned char *haystack_str;
@@ -460,17 +452,17 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	p1 = mem_get_int_unsafe(argv[1]);
 	if (p0type == MP_BIN) {
 		z = mem_as_bin(argv[0]);
-		len = mem_get_length(argv[0]);
+		len = mem_len_unsafe(argv[0]);
 		if (z == 0)
 			return;
-		assert(len == mem_get_length(argv[0]));
+		assert(len == mem_len_unsafe(argv[0]));
 	} else {
 		z = mem_as_ustr(argv[0]);
 		if (z == 0)
 			return;
 		len = 0;
 		if (p1 < 0)
-			len = sql_utf8_char_count(z, mem_get_length(argv[0]));
+			len = sql_utf8_char_count(z, mem_len_unsafe(argv[0]));
 	}
 	if (argc == 3) {
 		p2 = mem_get_int_unsafe(argv[2]);
@@ -509,7 +501,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 		 * used because '\0' is not supposed to be
 		 * end-of-string symbol.
 		 */
-		int byte_size = mem_get_length(argv[0]);
+		int byte_size = mem_len_unsafe(argv[0]);
 		int n_chars = sql_utf8_char_count(z, byte_size);
 		int cnt = 0;
 		int i = 0;
@@ -628,7 +620,7 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
 		return;                                                        \
 	}                                                                      \
 	z2 = mem_as_str0(argv[0]);                                             \
-	n = mem_get_length(argv[0]);                                           \
+	n = mem_len_unsafe(argv[0]);                                           \
 	/*                                                                     \
 	 * Verify that the call to _bytes()                                    \
 	 * does not invalidate the _text() pointer.                            \
@@ -965,15 +957,15 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 	}
 	const char *zB = mem_as_str0(argv[0]);
 	const char *zA = mem_as_str0(argv[1]);
-	const char *zB_end = zB + mem_get_length(argv[0]);
-	const char *zA_end = zA + mem_get_length(argv[1]);
+	const char *zB_end = zB + mem_len_unsafe(argv[0]);
+	const char *zA_end = zA + mem_len_unsafe(argv[1]);
 
 	/*
 	 * Limit the length of the LIKE pattern to avoid problems
 	 * of deep recursion and N*N behavior in
 	 * sql_utf8_pattern_compare().
 	 */
-	nPat = mem_get_length(argv[0]);
+	nPat = mem_len_unsafe(argv[0]);
 	testcase(nPat == db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH]);
 	testcase(nPat == db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH] + 1);
 	if (nPat > db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH]) {
@@ -994,7 +986,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		const unsigned char *zEsc = mem_as_ustr(argv[2]);
 		if (zEsc == 0)
 			return;
-		if (sql_utf8_char_count(zEsc, mem_get_length(argv[2])) != 1) {
+		if (sql_utf8_char_count(zEsc, mem_len_unsafe(argv[2])) != 1) {
 			diag_set(ClientError, ER_SQL_EXECUTE, "ESCAPE "\
 				 "expression must be a single character");
 			context->is_aborted = true;
@@ -1094,7 +1086,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_MAP: {
 			char *zText = 0;
 			char const *zBlob = mem_as_bin(argv[0]);
-			int nBlob = mem_get_length(argv[0]);
+			int nBlob = mem_len_unsafe(argv[0]);
 			assert(zBlob == mem_as_bin(argv[0]));	/* No encoding change */
 			zText =
 			    (char *)contextMalloc(context,
@@ -1229,7 +1221,7 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
 	pBlob = mem_as_bin(argv[0]);
-	n = mem_get_length(argv[0]);
+	n = mem_len_unsafe(argv[0]);
 	assert(pBlob == mem_as_bin(argv[0]));	/* No encoding change */
 	z = zHex = contextMalloc(context, ((i64) n) * 2 + 1);
 	if (zHex) {
@@ -1287,7 +1279,7 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 	zStr = mem_as_ustr(argv[0]);
 	if (zStr == 0)
 		return;
-	nStr = mem_get_length(argv[0]);
+	nStr = mem_len_unsafe(argv[0]);
 	assert(zStr == mem_as_ustr(argv[0]));	/* No encoding change */
 	zPattern = mem_as_ustr(argv[1]);
 	if (zPattern == 0) {
@@ -1295,7 +1287,7 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 		       || sql_context_db_handle(context)->mallocFailed);
 		return;
 	}
-	nPattern = mem_get_length(argv[1]);
+	nPattern = mem_len_unsafe(argv[1]);
 	if (nPattern == 0) {
 		assert(!mem_is_null(argv[1]));
 		sql_result_value(context, argv[0]);
@@ -1305,7 +1297,7 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 	zRep = mem_as_ustr(argv[2]);
 	if (zRep == 0)
 		return;
-	nRep = mem_get_length(argv[2]);
+	nRep = mem_len_unsafe(argv[2]);
 	assert(zRep == mem_as_ustr(argv[2]));
 	nOut = nStr + 1;
 	assert(nOut < SQL_MAX_LENGTH);
@@ -1467,7 +1459,7 @@ trim_func_one_arg(struct sql_context *context, sql_value *arg)
 		default_trim = (const unsigned char *) "\0";
 	else
 		default_trim = (const unsigned char *) " ";
-	int input_str_sz = mem_get_length(arg);
+	int input_str_sz = mem_len_unsafe(arg);
 	const unsigned char *input_str = mem_as_ustr(arg);
 	uint8_t trim_char_len[1] = { 1 };
 	trim_procedure(context, TRIM_BOTH, default_trim, trim_char_len, 1,
@@ -1493,14 +1485,14 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
 	if ((input_str = mem_as_ustr(arg2)) == NULL)
 		return;
 
-	int input_str_sz = mem_get_length(arg2);
+	int input_str_sz = mem_len_unsafe(arg2);
 	if (sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT) {
 		uint8_t len_one = 1;
 		trim_procedure(context, mem_get_int_unsafe(arg1),
 			       (const unsigned char *) " ", &len_one, 1,
 			       input_str, input_str_sz);
 	} else if ((trim_set = mem_as_ustr(arg1)) != NULL) {
-		int trim_set_sz = mem_get_length(arg1);
+		int trim_set_sz = mem_len_unsafe(arg1);
 		uint8_t *char_len;
 		int char_cnt = trim_prepare_char_len(context, trim_set,
 						     trim_set_sz, &char_len);
@@ -1529,8 +1521,8 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
 	    (trim_set = mem_as_ustr(arg2)) == NULL)
 		return;
 
-	int trim_set_sz = mem_get_length(arg2);
-	int input_str_sz = mem_get_length(arg3);
+	int trim_set_sz = mem_len_unsafe(arg2);
+	int input_str_sz = mem_len_unsafe(arg3);
 	uint8_t *char_len;
 	int char_cnt = trim_prepare_char_len(context, trim_set, trim_set_sz,
 					     &char_len);
@@ -1844,7 +1836,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 		if (!firstTerm) {
 			if (argc == 2) {
 				zSep = mem_as_str0(argv[1]);
-				nSep = mem_get_length(argv[1]);
+				nSep = mem_len_unsafe(argv[1]);
 			} else {
 				zSep = ",";
 				nSep = 1;
@@ -1853,7 +1845,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 				sqlStrAccumAppend(pAccum, zSep, nSep);
 		}
 		zVal = mem_as_str0(argv[0]);
-		nVal = mem_get_length(argv[0]);
+		nVal = mem_len_unsafe(argv[0]);
 		if (zVal)
 			sqlStrAccumAppend(pAccum, zVal, nVal);
 	}
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index e7612fe7e..a67345fbd 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -870,6 +870,20 @@ mem_get_bin(const struct Mem *mem, const char **s);
 int
 mem_get_bytes_len(const struct Mem *mem, uint32_t *len);
 
+/**
+ * Return length of value for MEM of STRING or VARBINARY type. This function is
+ * not safe since there is no proper processing in case mem_get_bytes_len()
+ * return an error. In this case this function returns 0.
+ */
+static inline int
+mem_len_unsafe(const struct Mem *mem)
+{
+	uint32_t len;
+	if (mem_get_bytes_len(mem, &len) != 0)
+		return 0;
+	return len;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.


New patch:


commit 20f06a5f2c52149060938136273b173790b11b47
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Mon Mar 22 12:27:33 2021 +0300

    sql: introduce mem_get_bytes_len()
    
    This patch introduces mem_get_bytes_len(). This function is used to
    receive length of string or binary value of MEM. If MEM is not
    of STRING or VARBINARy type this function returns -1.
    
    Part of #5818

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 8b4882b19..9c28d5122 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -187,14 +187,15 @@ lengthFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_UINT:
 	case MP_BOOL:
 	case MP_DOUBLE:{
-			sql_result_uint(context, sql_value_bytes(argv[0]));
+			mem_as_bin(argv[0]);
+			sql_result_uint(context, mem_len_unsafe(argv[0]));
 			break;
 		}
 	case MP_STR:{
 			const unsigned char *z = mem_as_ustr(argv[0]);
 			if (z == 0)
 				return;
-			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
+			len = sql_utf8_char_count(z, mem_len_unsafe(argv[0]));
 			sql_result_uint(context, len);
 			break;
 		}
@@ -304,8 +305,8 @@ position_func(struct sql_context *context, int argc, struct Mem **argv)
 		return;
 	}
 
-	int n_needle_bytes = sql_value_bytes(needle);
-	int n_haystack_bytes = sql_value_bytes(haystack);
+	int n_needle_bytes = mem_len_unsafe(needle);
+	int n_haystack_bytes = mem_len_unsafe(haystack);
 	int position = 1;
 	if (n_needle_bytes > 0) {
 		const unsigned char *haystack_str;
@@ -450,18 +451,18 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	p0type = sql_value_type(argv[0]);
 	p1 = mem_get_int_unsafe(argv[1]);
 	if (p0type == MP_BIN) {
-		len = sql_value_bytes(argv[0]);
 		z = mem_as_bin(argv[0]);
+		len = mem_len_unsafe(argv[0]);
 		if (z == 0)
 			return;
-		assert(len == sql_value_bytes(argv[0]));
+		assert(len == mem_len_unsafe(argv[0]));
 	} else {
 		z = mem_as_ustr(argv[0]);
 		if (z == 0)
 			return;
 		len = 0;
 		if (p1 < 0)
-			len = sql_utf8_char_count(z, sql_value_bytes(argv[0]));
+			len = sql_utf8_char_count(z, mem_len_unsafe(argv[0]));
 	}
 	if (argc == 3) {
 		p2 = mem_get_int_unsafe(argv[2]);
@@ -500,7 +501,7 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 		 * used because '\0' is not supposed to be
 		 * end-of-string symbol.
 		 */
-		int byte_size = sql_value_bytes(argv[0]);
+		int byte_size = mem_len_unsafe(argv[0]);
 		int n_chars = sql_utf8_char_count(z, byte_size);
 		int cnt = 0;
 		int i = 0;
@@ -619,7 +620,7 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
 		return;                                                        \
 	}                                                                      \
 	z2 = mem_as_str0(argv[0]);                                             \
-	n = sql_value_bytes(argv[0]);                                      \
+	n = mem_len_unsafe(argv[0]);                                           \
 	/*                                                                     \
 	 * Verify that the call to _bytes()                                    \
 	 * does not invalidate the _text() pointer.                            \
@@ -956,15 +957,15 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 	}
 	const char *zB = mem_as_str0(argv[0]);
 	const char *zA = mem_as_str0(argv[1]);
-	const char *zB_end = zB + sql_value_bytes(argv[0]);
-	const char *zA_end = zA + sql_value_bytes(argv[1]);
+	const char *zB_end = zB + mem_len_unsafe(argv[0]);
+	const char *zA_end = zA + mem_len_unsafe(argv[1]);
 
 	/*
 	 * Limit the length of the LIKE pattern to avoid problems
 	 * of deep recursion and N*N behavior in
 	 * sql_utf8_pattern_compare().
 	 */
-	nPat = sql_value_bytes(argv[0]);
+	nPat = mem_len_unsafe(argv[0]);
 	testcase(nPat == db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH]);
 	testcase(nPat == db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH] + 1);
 	if (nPat > db->aLimit[SQL_LIMIT_LIKE_PATTERN_LENGTH]) {
@@ -985,7 +986,7 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 		const unsigned char *zEsc = mem_as_ustr(argv[2]);
 		if (zEsc == 0)
 			return;
-		if (sql_utf8_char_count(zEsc, sql_value_bytes(argv[2])) != 1) {
+		if (sql_utf8_char_count(zEsc, mem_len_unsafe(argv[2])) != 1) {
 			diag_set(ClientError, ER_SQL_EXECUTE, "ESCAPE "\
 				 "expression must be a single character");
 			context->is_aborted = true;
@@ -1085,7 +1086,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_MAP: {
 			char *zText = 0;
 			char const *zBlob = mem_as_bin(argv[0]);
-			int nBlob = sql_value_bytes(argv[0]);
+			int nBlob = mem_len_unsafe(argv[0]);
 			assert(zBlob == mem_as_bin(argv[0]));	/* No encoding change */
 			zText =
 			    (char *)contextMalloc(context,
@@ -1220,7 +1221,7 @@ hexFunc(sql_context * context, int argc, sql_value ** argv)
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
 	pBlob = mem_as_bin(argv[0]);
-	n = sql_value_bytes(argv[0]);
+	n = mem_len_unsafe(argv[0]);
 	assert(pBlob == mem_as_bin(argv[0]));	/* No encoding change */
 	z = zHex = contextMalloc(context, ((i64) n) * 2 + 1);
 	if (zHex) {
@@ -1278,7 +1279,7 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 	zStr = mem_as_ustr(argv[0]);
 	if (zStr == 0)
 		return;
-	nStr = sql_value_bytes(argv[0]);
+	nStr = mem_len_unsafe(argv[0]);
 	assert(zStr == mem_as_ustr(argv[0]));	/* No encoding change */
 	zPattern = mem_as_ustr(argv[1]);
 	if (zPattern == 0) {
@@ -1286,7 +1287,7 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 		       || sql_context_db_handle(context)->mallocFailed);
 		return;
 	}
-	nPattern = sql_value_bytes(argv[1]);
+	nPattern = mem_len_unsafe(argv[1]);
 	if (nPattern == 0) {
 		assert(!mem_is_null(argv[1]));
 		sql_result_value(context, argv[0]);
@@ -1296,7 +1297,7 @@ replaceFunc(sql_context * context, int argc, sql_value ** argv)
 	zRep = mem_as_ustr(argv[2]);
 	if (zRep == 0)
 		return;
-	nRep = sql_value_bytes(argv[2]);
+	nRep = mem_len_unsafe(argv[2]);
 	assert(zRep == mem_as_ustr(argv[2]));
 	nOut = nStr + 1;
 	assert(nOut < SQL_MAX_LENGTH);
@@ -1458,7 +1459,7 @@ trim_func_one_arg(struct sql_context *context, sql_value *arg)
 		default_trim = (const unsigned char *) "\0";
 	else
 		default_trim = (const unsigned char *) " ";
-	int input_str_sz = sql_value_bytes(arg);
+	int input_str_sz = mem_len_unsafe(arg);
 	const unsigned char *input_str = mem_as_ustr(arg);
 	uint8_t trim_char_len[1] = { 1 };
 	trim_procedure(context, TRIM_BOTH, default_trim, trim_char_len, 1,
@@ -1484,14 +1485,14 @@ trim_func_two_args(struct sql_context *context, sql_value *arg1,
 	if ((input_str = mem_as_ustr(arg2)) == NULL)
 		return;
 
-	int input_str_sz = sql_value_bytes(arg2);
+	int input_str_sz = mem_len_unsafe(arg2);
 	if (sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT) {
 		uint8_t len_one = 1;
 		trim_procedure(context, mem_get_int_unsafe(arg1),
 			       (const unsigned char *) " ", &len_one, 1,
 			       input_str, input_str_sz);
 	} else if ((trim_set = mem_as_ustr(arg1)) != NULL) {
-		int trim_set_sz = sql_value_bytes(arg1);
+		int trim_set_sz = mem_len_unsafe(arg1);
 		uint8_t *char_len;
 		int char_cnt = trim_prepare_char_len(context, trim_set,
 						     trim_set_sz, &char_len);
@@ -1520,8 +1521,8 @@ trim_func_three_args(struct sql_context *context, sql_value *arg1,
 	    (trim_set = mem_as_ustr(arg2)) == NULL)
 		return;
 
-	int trim_set_sz = sql_value_bytes(arg2);
-	int input_str_sz = sql_value_bytes(arg3);
+	int trim_set_sz = mem_len_unsafe(arg2);
+	int input_str_sz = mem_len_unsafe(arg3);
 	uint8_t *char_len;
 	int char_cnt = trim_prepare_char_len(context, trim_set, trim_set_sz,
 					     &char_len);
@@ -1835,7 +1836,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 		if (!firstTerm) {
 			if (argc == 2) {
 				zSep = mem_as_str0(argv[1]);
-				nSep = sql_value_bytes(argv[1]);
+				nSep = mem_len_unsafe(argv[1]);
 			} else {
 				zSep = ",";
 				nSep = 1;
@@ -1844,7 +1845,7 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 				sqlStrAccumAppend(pAccum, zSep, nSep);
 		}
 		zVal = mem_as_str0(argv[0]);
-		nVal = sql_value_bytes(argv[0]);
+		nVal = mem_len_unsafe(argv[0]);
 		if (zVal)
 			sqlStrAccumAppend(pAccum, zVal, nVal);
 	}
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 2ad853a40..0addff161 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1156,6 +1156,18 @@ mem_get_bin(const struct Mem *mem, const char **s)
 	return 0;
 }
 
+int
+mem_get_bytes_len(const struct Mem *mem, uint32_t *len)
+{
+	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
+		return -1;
+	if ((mem->flags & MEM_Blob) !=0 && (mem->flags & MEM_Zero) != 0)
+		*len = mem->n + mem->u.nZero;
+	else
+		*len = mem->n;
+	return 0;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -1892,41 +1904,6 @@ sql_value_type(sql_value *pVal)
 	return mem_mp_type(mem);
 }
 
-/*
- * The sqlValueBytes() routine returns the number of bytes in the
- * sql_value object assuming that it uses the encoding "enc".
- * The valueBytes() routine is a helper function.
- */
-static SQL_NOINLINE int
-valueBytes(sql_value * pVal)
-{
-	if (mem_to_str(pVal) != 0)
-		return 0;
-	return pVal->n;
-}
-
-int
-sqlValueBytes(sql_value * pVal)
-{
-	Mem *p = (Mem *) pVal;
-	assert((p->flags & MEM_Null) == 0
-	       || (p->flags & (MEM_Str | MEM_Blob)) == 0);
-	if ((p->flags & MEM_Str) != 0) {
-		return p->n;
-	}
-	if ((p->flags & MEM_Blob) != 0) {
-		if (p->flags & MEM_Zero) {
-			return p->n + p->u.nZero;
-		} else {
-			return p->n;
-		}
-	}
-	if (p->flags & MEM_Null)
-		return 0;
-	return valueBytes(pVal);
-}
-
-
 #ifdef SQL_DEBUG
 /*
  * Check invariants on a Mem object.
@@ -2233,53 +2210,6 @@ releaseMemArray(Mem * p, int N)
 	}
 }
 
-int
-sql_value_bytes(sql_value * pVal)
-{
-	return sqlValueBytes(pVal);
-}
-
-/*
- * Return a pointer to static memory containing an SQL NULL value.
- */
-const Mem *
-columnNullValue(void)
-{
-	/* Even though the Mem structure contains an element
-	 * of type i64, on certain architectures (x86) with certain compiler
-	 * switches (-Os), gcc may align this Mem object on a 4-byte boundary
-	 * instead of an 8-byte one. This all works fine, except that when
-	 * running with SQL_DEBUG defined the sql code sometimes assert()s
-	 * that a Mem structure is located on an 8-byte boundary. To prevent
-	 * these assert()s from failing, when building with SQL_DEBUG defined
-	 * using gcc, we force nullMem to be 8-byte aligned using the magical
-	 * __attribute__((aligned(8))) macro.
-	 */
-	static const Mem nullMem
-#if defined(SQL_DEBUG) && defined(__GNUC__)
-	    __attribute__ ((aligned(8)))
-#endif
-	    = {
-		/* .u          = */  {
-		0},
-		    /* .flags      = */ (u16) MEM_Null,
-		    /* .eSubtype   = */ (u8) 0,
-		    /* .field_type = */ field_type_MAX,
-		    /* .n          = */ (int)0,
-		    /* .z          = */ (char *)0,
-		    /* .zMalloc    = */ (char *)0,
-		    /* .szMalloc   = */ (int)0,
-		    /* .uTemp      = */ (u32) 0,
-		    /* .db         = */ (sql *) 0,
-		    /* .xDel       = */ (void (*)(void *))0,
-#ifdef SQL_DEBUG
-		    /* .pScopyFrom = */ (Mem *) 0,
-		    /* .pFiller    = */ (void *)0,
-#endif
-	};
-	return &nullMem;
-}
-
 /*
  * Return true if the Mem object contains a TEXT or BLOB that is
  * too large - whose size exceeds SQL_MAX_LENGTH.
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 00c9bab36..a67345fbd 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -863,6 +863,27 @@ mem_as_str0(struct Mem *mem)
 int
 mem_get_bin(const struct Mem *mem, const char **s);
 
+/**
+ * Return length of value for MEM of STRING or VARBINARY type. Original MEM is
+ * not changed.
+ */
+int
+mem_get_bytes_len(const struct Mem *mem, uint32_t *len);
+
+/**
+ * Return length of value for MEM of STRING or VARBINARY type. This function is
+ * not safe since there is no proper processing in case mem_get_bytes_len()
+ * return an error. In this case this function returns 0.
+ */
+static inline int
+mem_len_unsafe(const struct Mem *mem)
+{
+	uint32_t len;
+	if (mem_get_bytes_len(mem, &len) != 0)
+		return 0;
+	return len;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -881,8 +902,6 @@ mem_mp_type(struct Mem *mem);
 enum mp_type
 sql_value_type(struct Mem *);
 
-int sqlValueBytes(struct Mem *);
-
 #ifdef SQL_DEBUG
 int sqlVdbeCheckMemInvariants(struct Mem *);
 void sqlVdbeMemPrettyPrint(Mem * pMem, char *zBuf);
@@ -912,18 +931,8 @@ struct Mem *sqlValueNew(struct sql *);
 void
 releaseMemArray(Mem * p, int N);
 
-/** Getters. */
-
-int
-sql_value_bytes(struct Mem *);
-
 #define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))])
 
-const Mem *
-columnNullValue(void);
-
-/** Checkers. */
-
 int sqlVdbeMemTooBig(Mem *);
 
 /* Return TRUE if Mem X contains dynamically allocated content - anything
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 8a7a87b49..8feb112f5 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -433,9 +433,6 @@ sql_stmt_compile(const char *sql, int bytes_count, struct Vdbe *re_prepared,
 int
 sql_step(sql_stmt *);
 
-int
-sql_column_bytes(sql_stmt *, int iCol);
-
 int
 sql_column_bytes16(sql_stmt *, int iCol);
 
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 5bb02e541..2bec2b186 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -422,41 +422,6 @@ sql_data_count(sql_stmt * pStmt)
 	return pVm->nResColumn;
 }
 
-/*
- * Check to see if column iCol of the given statement is valid.  If
- * it is, return a pointer to the Mem for the value of that column.
- * If iCol is not valid, return a pointer to a Mem which has a value
- * of NULL.
- */
-static Mem *
-columnMem(sql_stmt * pStmt, int i)
-{
-	Vdbe *pVm;
-	Mem *pOut;
-
-	pVm = (Vdbe *) pStmt;
-	if (pVm == 0)
-		return (Mem *) columnNullValue();
-	assert(pVm->db);
-	if (pVm->pResultSet != 0 && i < pVm->nResColumn && i >= 0) {
-		pOut = &pVm->pResultSet[i];
-	} else {
-		pOut = (Mem *) columnNullValue();
-	}
-	return pOut;
-}
-
-/**************************** sql_column_  ******************************
- * The following routines are used to access elements of the current row
- * in the result set.
- */
-
-int
-sql_column_bytes(sql_stmt * pStmt, int i)
-{
-	return sql_value_bytes(columnMem(pStmt, i));
-}
-
 char *
 sql_result_to_msgpack(struct sql_stmt *stmt, uint32_t *tuple_size,
 		      struct region *region)


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

* Re: [Tarantool-patches] [PATCH v5 47/52] sql: introduce mem_get_double()
  2021-04-14  1:00     ` Mergen Imeev via Tarantool-patches
@ 2021-04-15  0:17       ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-15  0:46         ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-15  0:17 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

Thanks for the fixes!

> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index f7b6df0d9..1deb8e507 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -2791,7 +2789,7 @@ case OP_SeekGT: {       /* jump, in3 */
>  			 *        (x >  4.9)    ->     (x >= 5)
>  			 *        (x <= 4.9)    ->     (x <  5)
>  			 */
> -			if (pIn3->u.r<(double)iKey) {
> +			if (mem_get_double_unsafe(pIn3) < (double)iKey) {

Why did you change this and below? There was a check above that
pIn3 is double. So you can access u.r safely.

>  				assert(OP_SeekGE==(OP_SeekGT-1));
>  				assert(OP_SeekLT==(OP_SeekLE-1));
>  				assert((OP_SeekLE & 0x0001)==(OP_SeekGT & 0x0001));
> @@ -2801,7 +2799,7 @@ case OP_SeekGT: {       /* jump, in3 */
>  			/* If the approximation iKey is smaller than the actual real search
>  			 * term, substitute <= for < and > for >=.
>  			 */
> -			else if (pIn3->u.r>(double)iKey) {
> +			else if (mem_get_double_unsafe(pIn3) > (double)iKey) {
>  				assert(OP_SeekLE==(OP_SeekLT+1));
>  				assert(OP_SeekGT==(OP_SeekGE+1));
>  				assert((OP_SeekLT & 0x0001)==(OP_SeekGE & 0x0001));

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

* Re: [Tarantool-patches] [PATCH v5 51/52] sql: introduce mem_get_bytes_len()
  2021-04-14  1:55     ` Mergen Imeev via Tarantool-patches
@ 2021-04-15  0:21       ` Vladislav Shpilevoy via Tarantool-patches
  2021-04-15  0:51         ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 37+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-15  0:21 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

Thanks for the patch!

> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index e7612fe7e..a67345fbd 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -870,6 +870,20 @@ mem_get_bin(const struct Mem *mem, const char **s);
>  int
>  mem_get_bytes_len(const struct Mem *mem, uint32_t *len);
>  
> +/**
> + * Return length of value for MEM of STRING or VARBINARY type. This function is
> + * not safe since there is no proper processing in case mem_get_bytes_len()
> + * return an error. In this case this function returns 0.
> + */
> +static inline int
> +mem_len_unsafe(const struct Mem *mem)
> +{
> +	uint32_t len;
> +	if (mem_get_bytes_len(mem, &len) != 0)

For the sake of consistency the safe function should be called
simply mem_len() IMO.

> +		return 0;
> +	return len;
> +}

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

* Re: [Tarantool-patches] [PATCH v5 47/52] sql: introduce mem_get_double()
  2021-04-15  0:17       ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-04-15  0:46         ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-15  0:46 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! I reverted these and one more place in vdbe.c
Sorry, there won't be diff, only a new patch.

On Thu, Apr 15, 2021 at 02:17:41AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> > index f7b6df0d9..1deb8e507 100644
> > --- a/src/box/sql/vdbe.c
> > +++ b/src/box/sql/vdbe.c
> > @@ -2791,7 +2789,7 @@ case OP_SeekGT: {       /* jump, in3 */
> >  			 *        (x >  4.9)    ->     (x >= 5)
> >  			 *        (x <= 4.9)    ->     (x <  5)
> >  			 */
> > -			if (pIn3->u.r<(double)iKey) {
> > +			if (mem_get_double_unsafe(pIn3) < (double)iKey) {
> 
> Why did you change this and below? There was a check above that
> pIn3 is double. So you can access u.r safely.
> 
I wanted to use mem_get_*() here, but you are right, it is not necessary here.
Fixed.

> >  				assert(OP_SeekGE==(OP_SeekGT-1));
> >  				assert(OP_SeekLT==(OP_SeekLE-1));
> >  				assert((OP_SeekLE & 0x0001)==(OP_SeekGT & 0x0001));
> > @@ -2801,7 +2799,7 @@ case OP_SeekGT: {       /* jump, in3 */
> >  			/* If the approximation iKey is smaller than the actual real search
> >  			 * term, substitute <= for < and > for >=.
> >  			 */
> > -			else if (pIn3->u.r>(double)iKey) {
> > +			else if (mem_get_double_unsafe(pIn3) > (double)iKey) {
> >  				assert(OP_SeekLE==(OP_SeekLT+1));
> >  				assert(OP_SeekGT==(OP_SeekGE+1));
> >  				assert((OP_SeekLT & 0x0001)==(OP_SeekGE & 0x0001));


New patch:


commit fd613a1c24938d79a68fd45e098660b4adc5ea11
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Wed Mar 17 14:13:30 2021 +0300

    sql: introduce mem_get_double()
    
    This patch introduces mem_get_double(). This function is used to receive
    double value from MEM. If value of MEM is not double, it is converted to
    double if possible. MEM is not changed.
    
    Part of #5818

diff --git a/src/box/sql/date.c b/src/box/sql/date.c
index dffc23616..dbf460498 100644
--- a/src/box/sql/date.c
+++ b/src/box/sql/date.c
@@ -929,7 +929,7 @@ isDate(sql_context * context, int argc, sql_value ** argv, DateTime * p)
 	}
 	if ((eType = sql_value_type(argv[0])) == MP_DOUBLE
 	    || eType == MP_INT) {
-		setRawDateNumber(p, sql_value_double(argv[0]));
+		setRawDateNumber(p, mem_get_double_unsafe(argv[0]));
 	} else {
 		z = sql_value_text(argv[0]);
 		if (!z || parseDateOrTime(context, (char *)z, p)) {
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 5503a9b16..dbf899c02 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -225,12 +225,11 @@ absFunc(sql_context * context, int argc, sql_value ** argv)
 		return;
 	}
 	default:{
-			/* Because sql_value_double() returns 0.0 if the argument is not
-			 * something that can be converted into a number, we have:
-			 * IMP: R-01992-00519 Abs(X) returns 0.0 if X is a string or blob
+			/*
+			 * Abs(X) returns 0.0 if X is a string or blob
 			 * that cannot be converted to a numeric value.
 			 */
-			double rVal = sql_value_double(argv[0]);
+			double rVal = mem_get_double_unsafe(argv[0]);
 			if (rVal < 0)
 				rVal = -rVal;
 			sql_result_double(context, rVal);
@@ -543,7 +542,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 		context->is_aborted = true;
 		return;
 	}
-	r = sql_value_double(argv[0]);
+	r = mem_get_double_unsafe(argv[0]);
 	/* If Y==0 and X will fit in a 64-bit int,
 	 * handle the rounding directly,
 	 * otherwise use printf.
@@ -1049,7 +1048,7 @@ quoteFunc(sql_context * context, int argc, sql_value ** argv)
 	case MP_DOUBLE:{
 			double r1, r2;
 			char zBuf[50];
-			r1 = sql_value_double(argv[0]);
+			r1 = mem_get_double_unsafe(argv[0]);
 			sql_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
 			sqlAtoF(zBuf, &r2, 20);
 			if (r1 != r2) {
@@ -1662,7 +1661,7 @@ sum_step(struct sql_context *context, int argc, sql_value **argv)
 			p->overflow = 1;
 		}
 	} else {
-		p->rSum += sql_value_double(argv[0]);
+		p->rSum += mem_get_double_unsafe(argv[0]);
 		p->approx = 1;
 	}
 }
diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 1197c2b68..00171ab55 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1110,6 +1110,29 @@ mem_get_uint(const struct Mem *mem, uint64_t *u)
 	return -1;
 }
 
+int
+mem_get_double(const struct Mem *mem, double *d)
+{
+	if ((mem->flags & MEM_Real) != 0) {
+		*d = mem->u.r;
+		return 0;
+	}
+	if ((mem->flags & MEM_Int) != 0) {
+		*d = (double)mem->u.i;
+		return 0;
+	}
+	if ((mem->flags & MEM_UInt) != 0) {
+		*d = (double)mem->u.u;
+		return 0;
+	}
+	if ((mem->flags & MEM_Str) != 0) {
+		if (sqlAtoF(mem->z, d, mem->n) == 0)
+			return -1;
+		return 0;
+	}
+	return -1;
+}
+
 int
 mem_copy(struct Mem *to, const struct Mem *from)
 {
@@ -2249,32 +2272,6 @@ mem_value_bool(const struct Mem *mem, bool *b)
 	return -1;
 }
 
-/*
- * Return the best representation of pMem that we can get into a
- * double.  If pMem is already a double or an integer, return its
- * value.  If it is a string or blob, try to convert it to a double.
- * If it is a NULL, return 0.0.
- */
-int
-sqlVdbeRealValue(Mem * pMem, double *v)
-{
-	assert(EIGHT_BYTE_ALIGNMENT(pMem));
-	if (pMem->flags & MEM_Real) {
-		*v = pMem->u.r;
-		return 0;
-	} else if (pMem->flags & MEM_Int) {
-		*v = (double)pMem->u.i;
-		return 0;
-	} else if ((pMem->flags & MEM_UInt) != 0) {
-		*v = (double)pMem->u.u;
-		return 0;
-	} else if (pMem->flags & MEM_Str) {
-		if (sqlAtoF(pMem->z, v, pMem->n))
-			return 0;
-	}
-	return -1;
-}
-
 /**************************** sql_value_  ******************************
  * The following routines extract information from a Mem or sql_value
  * structure.
@@ -2301,14 +2298,6 @@ sql_value_bytes(sql_value * pVal)
 	return sqlValueBytes(pVal);
 }
 
-double
-sql_value_double(sql_value * pVal)
-{
-	double v = 0.0;
-	sqlVdbeRealValue((Mem *) pVal, &v);
-	return v;
-}
-
 bool
 sql_value_boolean(sql_value *val)
 {
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index c2b337414..d283aa76a 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -790,6 +790,28 @@ mem_get_uint_unsafe(const struct Mem *mem)
 	return u;
 }
 
+/**
+ * Return value for MEM of DOUBLE type. For MEM of all other types convert
+ * value of the MEM to DOUBLE if possible and return converted value. Original
+ * MEM is not changed.
+ */
+int
+mem_get_double(const struct Mem *mem, double *d);
+
+/**
+ * Return value of MEM converted to double. This function is not safe since
+ * there is no proper processing in case mem_get_double() return an error. In
+ * this case this functions returns 0.
+ */
+static inline double
+mem_get_double_unsafe(const struct Mem *mem)
+{
+	double d;
+	if (mem_get_double(mem, &d) != 0)
+		return 0.;
+	return d;
+}
+
 /**
  * Simple type to str convertor. It is used to simplify
  * error reporting.
@@ -844,16 +866,12 @@ releaseMemArray(Mem * p, int N);
 
 int
 mem_value_bool(const struct Mem *mem, bool *b);
-int sqlVdbeRealValue(struct Mem *, double *);
 const void *
 sql_value_blob(struct Mem *);
 
 int
 sql_value_bytes(struct Mem *);
 
-double
-sql_value_double(struct Mem *);
-
 bool
 sql_value_boolean(struct Mem *val);
 
diff --git a/src/box/sql/printf.c b/src/box/sql/printf.c
index eb8413f9c..2f1948ff8 100644
--- a/src/box/sql/printf.c
+++ b/src/box/sql/printf.c
@@ -152,7 +152,7 @@ getDoubleArg(PrintfArguments * p)
 {
 	if (p->nArg <= p->nUsed)
 		return 0.0;
-	return sql_value_double(p->apArg[p->nUsed++]);
+	return mem_get_double_unsafe(p->apArg[p->nUsed++]);
 }
 
 static char *
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 9b706b218..a51a5d2d6 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -442,9 +442,6 @@ sql_column_bytes(sql_stmt *, int iCol);
 int
 sql_column_bytes16(sql_stmt *, int iCol);
 
-double
-sql_column_double(sql_stmt *, int iCol);
-
 bool
 sql_column_boolean(struct sql_stmt *stmt, int column);
 
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 1f84f5693..2308587e7 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -2751,24 +2751,22 @@ case OP_SeekGT: {       /* jump, in3 */
 		if (mem_is_str(pIn3))
 			mem_to_number(pIn3);
 		int64_t i;
-		if (mem_is_uint(pIn3)) {
-			i = pIn3->u.u;
-			is_neg = false;
-		} else if (mem_is_nint(pIn3)) {
-			i = pIn3->u.i;
-			is_neg = true;
-		} else if (mem_is_double(pIn3)) {
-			if (pIn3->u.r > (double)INT64_MAX)
+		if (mem_get_int(pIn3, &i, &is_neg) != 0) {
+			if (!mem_is_double(pIn3)) {
+				diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+					 mem_str(pIn3), "integer");
+				goto abort_due_to_error;
+			}
+			double d = pIn3->u.r;
+			assert(d >= (double)INT64_MAX || d < (double)INT64_MIN);
+			/* TODO: add [INT64_MAX, UINT64_MAX) here. */
+			if (d > (double)INT64_MAX)
 				i = INT64_MAX;
-			else if (pIn3->u.r < (double)INT64_MIN)
+			else if (d < (double)INT64_MIN)
 				i = INT64_MIN;
 			else
-				i = pIn3->u.r;
+				i = d;
 			is_neg = i < 0;
-		} else {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 mem_str(pIn3), "integer");
-			goto abort_due_to_error;
 		}
 		iKey = i;
 
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 24a61d07c..bc62de431 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -464,12 +464,6 @@ sql_column_bytes(sql_stmt * pStmt, int i)
 	return sql_value_bytes(columnMem(pStmt, i));
 }
 
-double
-sql_column_double(sql_stmt * pStmt, int i)
-{
-	return sql_value_double(columnMem(pStmt, i));
-}
-
 bool
 sql_column_boolean(struct sql_stmt *stmt, int i)
 {

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

* Re: [Tarantool-patches] [PATCH v5 51/52] sql: introduce mem_get_bytes_len()
  2021-04-15  0:21       ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-04-15  0:51         ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 37+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-15  0:51 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Thank you for the review! My answer and diff below.

On Thu, Apr 15, 2021 at 02:21:20AM +0200, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> > diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> > index e7612fe7e..a67345fbd 100644
> > --- a/src/box/sql/mem.h
> > +++ b/src/box/sql/mem.h
> > @@ -870,6 +870,20 @@ mem_get_bin(const struct Mem *mem, const char **s);
> >  int
> >  mem_get_bytes_len(const struct Mem *mem, uint32_t *len);
> >  
> > +/**
> > + * Return length of value for MEM of STRING or VARBINARY type. This function is
> > + * not safe since there is no proper processing in case mem_get_bytes_len()
> > + * return an error. In this case this function returns 0.
> > + */
> > +static inline int
> > +mem_len_unsafe(const struct Mem *mem)
> > +{
> > +	uint32_t len;
> > +	if (mem_get_bytes_len(mem, &len) != 0)
> 
> For the sake of consistency the safe function should be called
> simply mem_len() IMO.
> 
Fixed.

> > +		return 0;
> > +	return len;
> > +}


Diff:


diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 6edf3f9d5..af760f236 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1166,7 +1166,7 @@ mem_get_bin(const struct Mem *mem, const char **s)
 }
 
 int
-mem_get_bytes_len(const struct Mem *mem, uint32_t *len)
+mem_len(const struct Mem *mem, uint32_t *len)
 {
 	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
 		return -1;
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index a67345fbd..8e4b9f11a 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -868,18 +868,18 @@ mem_get_bin(const struct Mem *mem, const char **s);
  * not changed.
  */
 int
-mem_get_bytes_len(const struct Mem *mem, uint32_t *len);
+mem_len(const struct Mem *mem, uint32_t *len);
 
 /**
  * Return length of value for MEM of STRING or VARBINARY type. This function is
- * not safe since there is no proper processing in case mem_get_bytes_len()
- * return an error. In this case this function returns 0.
+ * not safe since there is no proper processing in case mem_len() return an
+ * error. In this case this function returns 0.
  */
 static inline int
 mem_len_unsafe(const struct Mem *mem)
 {
 	uint32_t len;
-	if (mem_get_bytes_len(mem, &len) != 0)
+	if (mem_len(mem, &len) != 0)
 		return 0;
 	return len;
 }

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

end of thread, other threads:[~2021-04-15  0:51 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <cover.1618000036.git.imeevma@gmail.com>
2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 41/52] sql: introduce mem_to_number() Mergen Imeev via Tarantool-patches
2021-04-13 23:25   ` Mergen Imeev via Tarantool-patches
2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 42/52] sql: introduce mem_to_str() and mem_to_str0() Mergen Imeev via Tarantool-patches
2021-04-13 22:58   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-13 23:41     ` Mergen Imeev via Tarantool-patches
2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 43/52] sql: introduce mem_cast_explicit() Mergen Imeev via Tarantool-patches
2021-04-13 22:59   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-14  0:01     ` Mergen Imeev via Tarantool-patches
2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 44/52] sql: introduce mem_cast_implicit() Mergen Imeev via Tarantool-patches
2021-04-13 22:59   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-14  0:05     ` Mergen Imeev via Tarantool-patches
2021-04-09 20:53 ` [Tarantool-patches] [PATCH v5 45/52] sql: introduce mem_get_int() Mergen Imeev via Tarantool-patches
2021-04-13 23:01   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-14  0:28     ` Mergen Imeev via Tarantool-patches
2021-04-14  1:17       ` Mergen Imeev via Tarantool-patches
2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 46/52] sql: introduce mem_get_uint() Mergen Imeev via Tarantool-patches
2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-14  0:39     ` Mergen Imeev via Tarantool-patches
2021-04-14  1:21       ` Mergen Imeev via Tarantool-patches
2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 47/52] sql: introduce mem_get_double() Mergen Imeev via Tarantool-patches
2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-14  1:00     ` Mergen Imeev via Tarantool-patches
2021-04-15  0:17       ` Vladislav Shpilevoy via Tarantool-patches
2021-04-15  0:46         ` Mergen Imeev via Tarantool-patches
2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 48/52] sql: introduce mem_get_bool() Mergen Imeev via Tarantool-patches
2021-04-13 23:04   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-14  1:29     ` Mergen Imeev via Tarantool-patches
2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 49/52] sql: introduce mem_get_str0() and mem_as_str0() Mergen Imeev via Tarantool-patches
2021-04-13 23:06   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-14  1:43     ` Mergen Imeev via Tarantool-patches
2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 50/52] sql: introduce mem_get_bin() Mergen Imeev via Tarantool-patches
2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 51/52] sql: introduce mem_get_bytes_len() Mergen Imeev via Tarantool-patches
2021-04-13 23:06   ` Vladislav Shpilevoy via Tarantool-patches
2021-04-14  1:55     ` Mergen Imeev via Tarantool-patches
2021-04-15  0:21       ` Vladislav Shpilevoy via Tarantool-patches
2021-04-15  0:51         ` Mergen Imeev via Tarantool-patches
2021-04-09 21:08 ` [Tarantool-patches] [PATCH v5 52/52] sql: introduce mem_get_agg() Mergen Imeev via Tarantool-patches

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