Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v2 0/3] Replace MEM-type flags by enum mem_type
@ 2021-04-27 16:55 Mergen Imeev via Tarantool-patches
  2021-04-27 16:55 ` [Tarantool-patches] [PATCH v2 3/3] sql: replace " Mergen Imeev via Tarantool-patches
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-27 16:55 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

After this patche-set, the MEM type could be determined by the value in the
special field, and not by the "flags" field.

https://github.com/tarantool/tarantool/issues/4906
https://github.com/tarantool/tarantool/tree/imeevma/gh-4906-rework-mem-types

Changes in v2:
 - Added two new commits.

Mergen Imeev (3):
  sql: initialize MEM used in aggregate functions
  sql: make mem_is_bin() to check only for VARBINARY
  sql: replace MEM-type flags by enum mem_type

 src/box/sql/func.c    |  31 +-
 src/box/sql/mem.c     | 954 +++++++++++++++++++++++-------------------
 src/box/sql/mem.h     | 199 +++++----
 src/box/sql/sqlInt.h  |   7 +
 src/box/sql/vdbe.c    |  16 +-
 src/box/sql/vdbeapi.c |  20 +
 src/box/sql/vdbemem.c |   3 +-
 7 files changed, 673 insertions(+), 557 deletions(-)

-- 
2.25.1


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

* [Tarantool-patches] [PATCH v2 3/3] sql: replace MEM-type flags by enum mem_type
  2021-04-27 16:55 [Tarantool-patches] [PATCH v2 0/3] Replace MEM-type flags by enum mem_type Mergen Imeev via Tarantool-patches
@ 2021-04-27 16:55 ` Mergen Imeev via Tarantool-patches
  2021-04-29 21:09   ` Vladislav Shpilevoy via Tarantool-patches
  2021-05-25 21:33 ` [Tarantool-patches] [PATCH v2 0/3] Replace " Vladislav Shpilevoy via Tarantool-patches
  2021-05-26  8:06 ` Kirill Yukhin via Tarantool-patches
  2 siblings, 1 reply; 11+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-04-27 16:55 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

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

On 25.04.2021 20:41, Vladislav Shpilevoy wrote:
> Hi! Thanks for the patch!
>
> See 15 comments below. Most of them are the same though.
>
>> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
>> index 9c28d5122..2ce7a3871 100644
>> --- a/src/box/sql/func.c
>> +++ b/src/box/sql/func.c
>> @@ -1766,14 +1766,20 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
>>  
>>  	struct func_sql_builtin *func =
>>  		(struct func_sql_builtin *)context->func;
>> +	/*
>> +	 * TODO: Make proper initialization for aggregate accumulation
>> +	 * structures. Currently a hack is applied here: in case mem pBest is a
>> +	 * newly created MEM, it bytes are set to 0 using memset(). Since
>> +	 * MEM_NULL == 0, it works. However, it does not look right.
>> +	 */
>
> 1. Not sure I understand. I see sql_aggregate_context() calls mem_set_agg()
> which sets MEM_AGG. Isn't it a proper initialization?
>
Not exactly. Memory that contains MEM is memset to 0, which is not exactly
consistent state for a MEM. Moved this to another patch.

>>  	pBest = (Mem *) sql_aggregate_context(context, sizeof(*pBest));
>>  	if (!pBest)
>>  		return;
>> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
>> index b6ff6397f..90883ee21 100644
>> --- a/src/box/sql/mem.c
>> +++ b/src/box/sql/mem.c
>> @@ -60,26 +60,26 @@ const char *
>>  mem_str(const struct Mem *mem)
>>  {
>>  	char buf[BUF_SIZE];
>> -	switch (mem->flags & MEM_PURE_TYPE_MASK) {
>> -	case MEM_Null:
>> +	switch (mem->type) {
>> +	case MEM_NULL:
>
> 2. Better use MEM_TYPE_*. Otherwise in case the mem will have
> any other enums, the value names would confuse. It is already
> so because the flags also have MEM_ prefix and the only
> difference is they are not uppercase in the suffix.
>
Thanks, fixed.

>> @@ -149,7 +148,8 @@ mem_set_int(struct Mem *mem, int64_t value, bool is_neg)
>>  {
>>  	mem_clear(mem);
>>  	mem->u.i = value;
>> -	mem->flags = is_neg ? MEM_Int : MEM_UInt;
>> +	mem->type = is_neg ? MEM_INT : MEM_UINT;
>> +	mem->flags = 0;
>
> 3. The flags are already cleared by mem_clear(). The same in
> mem_set_uint(), mem_set_bool(), mem_set_double().
>
Fixed. Replaced assignment in all such places by assert.

>> @@ -281,7 +287,8 @@ mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
>>  		return -1;
>>  	memcpy(mem->z, value, len);
>>  	mem->n = len;
>> -	mem->flags = MEM_Str;
>> +	mem->type = MEM_STR;
>> +	mem->flags = 0;
> 4. Ditto. The same in mem_copy_bin().
>
Fixed.

>>  	mem->field_type = FIELD_TYPE_STRING;
>>  	return 0;
>>  }
>> @@ -459,14 +471,16 @@ void
>>  mem_set_invalid(struct Mem *mem)
>>  {
>>  	mem_clear(mem);
>> -	mem->flags = MEM_Undefined;
>> +	mem->type = MEM_INVALID;
>> +	mem->flags = 0;
>
> 5. The same as above + mem_set_ptr(), mem_set_frame(),
> mem_set_agg().
>
Fixed.

>> @@ -498,19 +514,21 @@ void
>>  mem_set_null_clear(struct Mem *mem)
>>  {
>>  	mem_clear(mem);
>> -	mem->flags = MEM_Null | MEM_Cleared;
>> +	mem->flags = MEM_Cleared;
>>  }
>>  
>>  static inline int
>>  int_to_double(struct Mem *mem)
>>  {
>> +	assert(mem->type == MEM_INT || mem->type == MEM_UINT);
>>  	double d;
>> -	if ((mem->flags & MEM_UInt) != 0)
>> +	if (mem->type == MEM_UINT)
>>  		d = (double)mem->u.u;
>>  	else
>>  		d = (double)mem->u.i;
>>  	mem->u.r = d;
>> -	mem->flags = MEM_Real;
>> +	mem->type = MEM_DOUBLE;
>> +	mem->flags = 0;
>
> 6. Do you need to touch the flags? If it was an int, its flags
> should have been 0 already. The same in int_to_bool(),
> double_to_int(), double_to_int_precise(), double_to_uint(),
> double_to_uint_precise(), double_to_bool(), bool_to_int().
>
Fixed.

>> @@ -550,8 +571,9 @@ str_to_str0(struct Mem *mem)
>>  static inline int
>>  str_to_bin(struct Mem *mem)
>>  {
>> -	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
>> -		     MEM_Blob;
>> +	assert(mem->type == MEM_STR);
>> +	mem->type = MEM_BIN;
>> +	mem->flags = mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem);
>
> 7. You can now use &=. But did you check if you even need to touch
> these flags? It seems you can leave them as is. Previously they
> were reset just to drop MEM_Blob flag I think.
>
There is possiblity that MEM_Term flag was set. Fixed.

> The same in bin_to_str().
>
Removed in bin_to_str().

>>  	mem->field_type = FIELD_TYPE_VARBINARY;
>>  	return 0;
>>  }> @@ -1168,9 +1229,9 @@ mem_get_bin(const struct Mem *mem, const char **s)
>>  int
>>  mem_len(const struct Mem *mem, uint32_t *len)
>>  {
>> -	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
>> +	if (mem->type != MEM_STR && mem->type != MEM_BIN)
>>  		return -1;
>
> 8. Is it -1 for MAP and ARRAY intentionally? Previously they
> were included into MEM_Blob.
>
> I see mem_concat() didn't check for subtypes. Does it mean we
> could concat two MP_ARRAYs into something invalid? If we could,
> your patch probably just fixed it. Could you check and add a
> test if so?
>
Fixed. You were right, it is actually possible to concatenate two maps, map and
array, map and varbinary and so on. I returned ability to do this. Should I add
a test?

>> -	if ((mem->flags & MEM_Blob) !=0 && (mem->flags & MEM_Zero) != 0)
>> +	if (mem->type == MEM_BIN && (mem->flags & MEM_Zero) != 0)
>>  		*len = mem->n + mem->u.nZero;
>>  	else
>>  		*len = mem->n;
>> @@ -1236,7 +1298,8 @@ mem_move(struct Mem *to, struct Mem *from)
>>  {
>>  	mem_destroy(to);
>>  	memcpy(to, from, sizeof(*to));
>> -	from->flags = MEM_Null;
>> +	from->type = MEM_NULL;
>> +	from->flags = 0;
>
> 9. Flags are already 0 after mem_destroy(). The same in mem_add(),
> mem_sub(), mem_mul(), mem_div(), mem_rem(), mem_bit_and(),
> mem_bit_or(), mem_shift_left(), mem_shift_right(), mem_bit_not().
>
Fixed.

>> @@ -2208,7 +2264,8 @@ sqlValueNew(sql * db)
>>  {
>>  	Mem *p = sqlDbMallocZero(db, sizeof(*p));
>>  	if (p) {
>> -		p->flags = MEM_Null;
>> +		p->type = MEM_NULL;
>> +		p->flags = 0;
>
> 10. MallocZero nullifies the memory, no need to set the flags to 0.
>
Fixed.

>>  		p->db = db;
>>  	}
>>  	return p;
>> @@ -2223,7 +2280,8 @@ releaseMemArray(Mem * p, int N)
>>  			assert((&p[1]) == pEnd || p[0].db == p[1].db);
>>  			assert(sqlVdbeCheckMemInvariants(p));
>>  			mem_destroy(p);
>> -			p->flags = MEM_Undefined;
>> +			p->type = MEM_INVALID;
>> +			p->flags = 0;
>
> 11. mem_destroy() already cleared the flags.
>
Fixed.

>>  		} while ((++p) < pEnd);
>>  	}
>>  }
>> @@ -2844,23 +2919,28 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size)
>>  		mem_clear(&val[i]);
>>  		switch (field.type) {
>>  		case MP_BOOL:
>> -			val[i].flags = MEM_Bool;
>> +			val[i].type = MEM_BOOL;
>> +			val[i].flags = 0;
>
> 12. The flags are cleared by mem_clear() above. The same in the other places
> in this function. The same in port_c_get_vdbemem().
>
Fixed.

>> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
>> index 1db7f4deb..48c1850d4 100644
>> --- a/src/box/sql/mem.h
>> +++ b/src/box/sql/mem.h
>> @@ -37,6 +37,35 @@ struct region;
>>  struct mpstream;
>>  struct VdbeFrame;
>>  
>> +enum mem_type {
>> +	MEM_NULL = 0,
>> +	MEM_UINT,
>> +	MEM_INT,
>> +	MEM_STR,
>> +	MEM_BIN,
>> +	MEM_ARRAY,
>> +	MEM_MAP,
>> +	MEM_BOOL,
>> +	MEM_FLOAT,
>> +	MEM_DOUBLE,
>> +	MEM_INVALID,
>> +	MEM_FRAME,
>> +	MEM_PTR,
>> +	MEM_AGG,
>> +};
>> +
>> +static_assert((int)MP_NIL == (int)MEM_NULL);
>> +static_assert((int)MP_UINT == (int)MEM_UINT);
>> +static_assert((int)MP_INT == (int)MEM_INT);
>> +static_assert((int)MP_STR == (int)MEM_STR);
>> +static_assert((int)MP_BIN == (int)MEM_BIN);
>> +static_assert((int)MP_ARRAY == (int)MEM_ARRAY);
>> +static_assert((int)MP_MAP == (int)MEM_MAP);
>> +static_assert((int)MP_BOOL == (int)MEM_BOOL);
>> +static_assert((int)MP_FLOAT == (int)MEM_FLOAT);
>> +static_assert((int)MP_DOUBLE == (int)MEM_DOUBLE);
>> +static_assert((int)MP_EXT == (int)MEM_INVALID);
>
> 13. You could also assign them right inside of enum declaration. Then
> wouldn't need to assert.
>
Removed these asserts.

>> +
>>  /*
>>   * Internally, the vdbe manipulates nearly all SQL values as Mem
>>   * structures. Each Mem struct may cache multiple representations (string,
>> @@ -57,9 +86,8 @@ struct Mem {
>>  		struct func *func;
>>  		struct VdbeFrame *pFrame;	/* Used when flags==MEM_Frame */
>>  	} u;
>> -	u32 flags;		/* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
>> -	/** Subtype for this value. */
>> -	enum sql_subtype subtype;
>> +	enum mem_type type;
>> +	u32 flags;		/* Some combination of MEM_Term, MEM_Zero, MEM_Dyn, etc. */
>
> 14. Since you changed the line, it is better to put the comment on
> top of the member, according to the code style. And add a comment to the
> type field. Even a trivial one would work. Comments between members help
> to make the struct look more 'structured'.
>
Fixed. Also, I reworked all comments and removed uTemp and pFiller fields.

>> @@ -114,175 +120,153 @@ struct Mem {
>
> <...>
>
>>  static inline bool
>>  mem_is_agg(const struct Mem *mem)
>>  {
>> -	return (mem->flags & MEM_Agg) != 0;
>> +	return mem->type == MEM_AGG;
>>  }
>>  
>>  static inline bool
>>  mem_is_bytes(const struct Mem *mem)
>>  {
>> -	return (mem->flags & (MEM_Blob | MEM_Str)) != 0;
>> +	enum mem_type type = mem->type;
>> +	return type == MEM_BIN || type == MEM_MAP || type == MEM_ARRAY ||
>> +	       type == MEM_STR;
>>  }
> 15. It was good when we could check several rare cases at one
> branch. Maybe you could preserve the bitwise structure of the
> types? Then we could keep the bit operations and save a few
> branches. You didn't think of it, or do we go for normal numbers
> intentionally?
>
I did it, although I'm not sure if this is the right choice: although it makes
our code more compact in some cases, it makes it a little less readable.

> On the other hand, having the types equal to mp types wins when
> need to convert mem_type to mp_type, but do we need it often?
Not often, especially if we exclude cases in sql/func.c.


New patch:

commit 3f495b512c6d1a0e494c111ad37083f98c9988ad
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Fri Apr 16 18:06:31 2021 +0300

    sql: replace MEM-type flags by enum mem_type
    
    This patch moves MEM types from the 'u32 flags' field to the new
    'enum mem_type type' field. Now, we can be sure that only one type is
    set for MEM. In addition, it is now easier to distinguish MAP and ARRAY
    from VARBINARY, and this makes it easier to add extension types - UUID
    and DECIMAL.
    
    Closes #4906
    Needed for #5886

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index b6ff6397f..4f189cac4 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -60,26 +60,26 @@ const char *
 mem_str(const struct Mem *mem)
 {
 	char buf[BUF_SIZE];
-	switch (mem->flags & MEM_PURE_TYPE_MASK) {
-	case MEM_Null:
+	switch (mem->type) {
+	case MEM_TYPE_NULL:
 		return "NULL";
-	case MEM_Str:
+	case MEM_TYPE_STR:
 		if ((mem->flags & MEM_Term) != 0)
 			return mem->z;
 		return tt_cstr(mem->z, mem->n);
-	case MEM_Int:
+	case MEM_TYPE_INT:
 		return tt_sprintf("%lld", mem->u.i);
-	case MEM_UInt:
+	case MEM_TYPE_UINT:
 		return tt_sprintf("%llu", mem->u.u);
-	case MEM_Real:
+	case MEM_TYPE_DOUBLE:
 		sql_snprintf(BUF_SIZE, &buf[0], "%!.15g", mem->u.r);
 		return tt_sprintf("%s", buf);
-	case MEM_Blob:
-		if ((mem->flags & MEM_Subtype) == 0)
-			return "varbinary";
-		assert(mem->subtype == SQL_SUBTYPE_MSGPACK);
+	case MEM_TYPE_BIN:
+		return "varbinary";
+	case MEM_TYPE_MAP:
+	case MEM_TYPE_ARRAY:
 		return mp_str(mem->z);
-	case MEM_Bool:
+	case MEM_TYPE_BOOL:
 		return mem->u.b ? "TRUE" : "FALSE";
 	default:
 		return "unknown";
@@ -89,39 +89,36 @@ mem_str(const struct Mem *mem)
 void
 mem_create(struct Mem *mem)
 {
-	mem->flags = MEM_Null;
-	mem->subtype = SQL_SUBTYPE_NO;
+	mem->type = MEM_TYPE_NULL;
+	mem->flags = 0;
 	mem->field_type = field_type_MAX;
 	mem->n = 0;
 	mem->z = NULL;
 	mem->zMalloc = NULL;
 	mem->szMalloc = 0;
-	mem->uTemp = 0;
 	mem->db = sql_get();
 	mem->xDel = NULL;
 #ifdef SQL_DEBUG
 	mem->pScopyFrom = NULL;
-	mem->pFiller = NULL;
 #endif
 }
 
 static inline void
 mem_clear(struct Mem *mem)
 {
-	if ((mem->flags & (MEM_Agg | MEM_Dyn | MEM_Frame)) != 0) {
-		if ((mem->flags & MEM_Agg) != 0)
-			sql_vdbemem_finalize(mem, mem->u.func);
-		assert((mem->flags & MEM_Agg) == 0);
-		if ((mem->flags & MEM_Dyn) != 0) {
-			assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
-			mem->xDel((void *)mem->z);
-		} else if ((mem->flags & MEM_Frame) != 0) {
-			struct VdbeFrame *frame = mem->u.pFrame;
-			frame->pParent = frame->v->pDelFrame;
-			frame->v->pDelFrame = frame;
-		}
-	}
-	mem->flags = MEM_Null;
+	if (mem->type == MEM_TYPE_AGG) {
+		sql_vdbemem_finalize(mem, mem->u.func);
+		assert(mem->type != MEM_TYPE_AGG);
+	} else if (mem->type == MEM_TYPE_FRAME) {
+		struct VdbeFrame *frame = mem->u.pFrame;
+		frame->pParent = frame->v->pDelFrame;
+		frame->v->pDelFrame = frame;
+	} else if ((mem->flags & MEM_Dyn) != 0) {
+		assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
+		mem->xDel((void *)mem->z);
+	}
+	mem->type = MEM_TYPE_NULL;
+	mem->flags = 0;
 	mem->field_type = field_type_MAX;
 }
 
@@ -149,7 +146,8 @@ mem_set_int(struct Mem *mem, int64_t value, bool is_neg)
 {
 	mem_clear(mem);
 	mem->u.i = value;
-	mem->flags = is_neg ? MEM_Int : MEM_UInt;
+	mem->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_INTEGER;
 }
 
@@ -158,7 +156,8 @@ mem_set_uint(struct Mem *mem, uint64_t value)
 {
 	mem_clear(mem);
 	mem->u.u = value;
-	mem->flags = MEM_UInt;
+	mem->type = MEM_TYPE_UINT;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_UNSIGNED;
 }
 
@@ -167,7 +166,8 @@ mem_set_bool(struct Mem *mem, bool value)
 {
 	mem_clear(mem);
 	mem->u.b = value;
-	mem->flags = MEM_Bool;
+	mem->type = MEM_TYPE_BOOL;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_BOOLEAN;
 }
 
@@ -176,10 +176,11 @@ mem_set_double(struct Mem *mem, double value)
 {
 	mem_clear(mem);
 	mem->field_type = FIELD_TYPE_DOUBLE;
+	assert(mem->flags == 0);
 	if (sqlIsNaN(value))
 		return;
 	mem->u.r = value;
-	mem->flags = MEM_Real;
+	mem->type = MEM_TYPE_DOUBLE;
 }
 
 static inline void
@@ -189,7 +190,8 @@ set_str_const(struct Mem *mem, char *value, uint32_t len, int alloc_type)
 	mem_clear(mem);
 	mem->z = value;
 	mem->n = len;
-	mem->flags = MEM_Str | alloc_type;
+	mem->type = MEM_TYPE_STR;
+	mem->flags = alloc_type;
 	mem->field_type = FIELD_TYPE_STRING;
 }
 
@@ -202,7 +204,8 @@ set_str_dynamic(struct Mem *mem, char *value, uint32_t len, int alloc_type)
 	mem_destroy(mem);
 	mem->z = value;
 	mem->n = len;
-	mem->flags = MEM_Str | alloc_type;
+	mem->type = MEM_TYPE_STR;
+	mem->flags = alloc_type;
 	mem->field_type = FIELD_TYPE_STRING;
 	if (alloc_type == MEM_Dyn) {
 		mem->xDel = sql_free;
@@ -268,11 +271,13 @@ mem_set_str0_allocated(struct Mem *mem, char *value)
 int
 mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
 {
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0 && mem->z == value) {
+	if ((mem->type == MEM_TYPE_STR ||
+	     mem->type == MEM_TYPE_BIN) && mem->z == value) {
 		/* Own value, but might be ephemeral. Make it own if so. */
 		if (sqlVdbeMemGrow(mem, len, 1) != 0)
 			return -1;
-		mem->flags = MEM_Str;
+		mem->type = MEM_TYPE_STR;
+		mem->flags = 0;
 		mem->field_type = FIELD_TYPE_STRING;
 		return 0;
 	}
@@ -281,7 +286,8 @@ mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
 		return -1;
 	memcpy(mem->z, value, len);
 	mem->n = len;
-	mem->flags = MEM_Str;
+	mem->type = MEM_TYPE_STR;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_STRING;
 	return 0;
 }
@@ -304,7 +310,8 @@ set_bin_const(struct Mem *mem, char *value, uint32_t size, int alloc_type)
 	mem_clear(mem);
 	mem->z = value;
 	mem->n = size;
-	mem->flags = MEM_Blob | alloc_type;
+	mem->type = MEM_TYPE_BIN;
+	mem->flags = alloc_type;
 	mem->field_type = FIELD_TYPE_VARBINARY;
 }
 
@@ -317,7 +324,8 @@ set_bin_dynamic(struct Mem *mem, char *value, uint32_t size, int alloc_type)
 	mem_destroy(mem);
 	mem->z = value;
 	mem->n = size;
-	mem->flags = MEM_Blob | alloc_type;
+	mem->type = MEM_TYPE_BIN;
+	mem->flags = alloc_type;
 	mem->field_type = FIELD_TYPE_VARBINARY;
 	if (alloc_type == MEM_Dyn) {
 		mem->xDel = sql_free;
@@ -355,11 +363,13 @@ mem_set_bin_allocated(struct Mem *mem, char *value, uint32_t size)
 int
 mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
 {
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0 && mem->z == value) {
+	if ((mem->type == MEM_TYPE_STR ||
+	     mem->type == MEM_TYPE_BIN) && mem->z == value) {
 		/* Own value, but might be ephemeral. Make it own if so. */
 		if (sqlVdbeMemGrow(mem, size, 1) != 0)
 			return -1;
-		mem->flags = MEM_Blob;
+		mem->type = MEM_TYPE_BIN;
+		mem->flags = 0;
 		mem->field_type = FIELD_TYPE_VARBINARY;
 		return 0;
 	}
@@ -368,7 +378,8 @@ mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
 		return -1;
 	memcpy(mem->z, value, size);
 	mem->n = size;
-	mem->flags = MEM_Blob;
+	mem->type = MEM_TYPE_BIN;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_VARBINARY;
 	return 0;
 }
@@ -382,7 +393,8 @@ mem_set_zerobin(struct Mem *mem, int n)
 	mem->u.nZero = n;
 	mem->z = NULL;
 	mem->n = 0;
-	mem->flags = MEM_Blob | MEM_Zero;
+	mem->type = MEM_TYPE_BIN;
+	mem->flags = MEM_Zero;
 	mem->field_type = FIELD_TYPE_VARBINARY;
 }
 
@@ -390,12 +402,12 @@ static inline void
 set_msgpack_value(struct Mem *mem, char *value, uint32_t size, int alloc_type,
 		  enum field_type type)
 {
+	assert(type == FIELD_TYPE_MAP || type == FIELD_TYPE_ARRAY);
 	if (alloc_type == MEM_Ephem || alloc_type == MEM_Static)
 		set_bin_const(mem, value, size, alloc_type);
 	else
 		set_bin_dynamic(mem, value, size, alloc_type);
-	mem->flags |= MEM_Subtype;
-	mem->subtype = SQL_SUBTYPE_MSGPACK;
+	mem->type = type == FIELD_TYPE_MAP ? MEM_TYPE_MAP : MEM_TYPE_ARRAY;
 	mem->field_type = type;
 }
 
@@ -459,14 +471,16 @@ void
 mem_set_invalid(struct Mem *mem)
 {
 	mem_clear(mem);
-	mem->flags = MEM_Undefined;
+	mem->type = MEM_TYPE_INVALID;
+	assert(mem->flags == 0);
 }
 
 void
 mem_set_ptr(struct Mem *mem, void *ptr)
 {
 	mem_clear(mem);
-	mem->flags = MEM_Ptr;
+	mem->type = MEM_TYPE_PTR;
+	assert(mem->flags == 0);
 	mem->u.p = ptr;
 }
 
@@ -474,7 +488,8 @@ void
 mem_set_frame(struct Mem *mem, struct VdbeFrame *frame)
 {
 	mem_clear(mem);
-	mem->flags = MEM_Frame;
+	mem->type = MEM_TYPE_FRAME;
+	assert(mem->flags == 0);
 	mem->u.pFrame = frame;
 }
 
@@ -488,7 +503,8 @@ mem_set_agg(struct Mem *mem, struct func *func, int size)
 		return -1;
 	memset(mem->z, 0, size);
 	mem->n = size;
-	mem->flags = MEM_Agg;
+	mem->type = MEM_TYPE_AGG;
+	assert(mem->flags == 0);
 	mem->u.func = func;
 	mem->field_type = field_type_MAX;
 	return 0;
@@ -498,19 +514,21 @@ void
 mem_set_null_clear(struct Mem *mem)
 {
 	mem_clear(mem);
-	mem->flags = MEM_Null | MEM_Cleared;
+	mem->flags = MEM_Cleared;
 }
 
 static inline int
 int_to_double(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
 	double d;
-	if ((mem->flags & MEM_UInt) != 0)
+	if (mem->type == MEM_TYPE_UINT)
 		d = (double)mem->u.u;
 	else
 		d = (double)mem->u.i;
 	mem->u.r = d;
-	mem->flags = MEM_Real;
+	mem->type = MEM_TYPE_DOUBLE;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_DOUBLE;
 	return 0;
 }
@@ -518,8 +536,9 @@ int_to_double(struct Mem *mem)
 static inline int
 int_to_str0(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
 	const char *str;
-	if ((mem->flags & MEM_UInt) != 0)
+	if (mem->type == MEM_TYPE_UINT)
 		str = tt_sprintf("%llu", mem->u.u);
 	else
 		str = tt_sprintf("%lld", mem->u.i);
@@ -529,8 +548,10 @@ int_to_str0(struct Mem *mem)
 static inline int
 int_to_bool(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
 	mem->u.b = mem->u.i != 0;
-	mem->flags = MEM_Bool;
+	mem->type = MEM_TYPE_BOOL;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_BOOLEAN;
 	return 0;
 }
@@ -538,7 +559,7 @@ int_to_bool(struct Mem *mem)
 static inline int
 str_to_str0(struct Mem *mem)
 {
-	assert((mem->flags | MEM_Str) != 0);
+	assert(mem->type == MEM_TYPE_STR);
 	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
 		return -1;
 	mem->z[mem->n] = '\0';
@@ -550,8 +571,9 @@ str_to_str0(struct Mem *mem)
 static inline int
 str_to_bin(struct Mem *mem)
 {
-	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
-		     MEM_Blob;
+	assert(mem->type == MEM_TYPE_STR);
+	mem->type = MEM_TYPE_BIN;
+	mem->flags &= ~MEM_Term;
 	mem->field_type = FIELD_TYPE_VARBINARY;
 	return 0;
 }
@@ -559,6 +581,7 @@ str_to_bin(struct Mem *mem)
 static inline int
 str_to_bool(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_STR);
 	char *str = mem->z;
 	bool b;
 	const char *str_true = "TRUE";
@@ -586,10 +609,10 @@ str_to_bool(struct Mem *mem)
 static inline int
 bin_to_str(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_BIN);
 	if (ExpandBlob(mem) != 0)
 		return -1;
-	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
-		      MEM_Str;
+	mem->type = MEM_TYPE_STR;
 	mem->field_type = FIELD_TYPE_STRING;
 	return 0;
 }
@@ -597,12 +620,14 @@ bin_to_str(struct Mem *mem)
 static inline int
 bin_to_str0(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_BIN);
 	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->type = MEM_TYPE_STR;
+	mem->flags = MEM_Term;
 	mem->field_type = FIELD_TYPE_STRING;
 	return 0;
 }
@@ -610,6 +635,7 @@ bin_to_str0(struct Mem *mem)
 static inline int
 bytes_to_int(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN);
 	bool is_neg;
 	int64_t i;
 	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
@@ -621,6 +647,7 @@ bytes_to_int(struct Mem *mem)
 static inline int
 bytes_to_uint(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN);
 	bool is_neg;
 	int64_t i;
 	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
@@ -634,6 +661,7 @@ bytes_to_uint(struct Mem *mem)
 static inline int
 bytes_to_double(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN);
 	double d;
 	if (sqlAtoF(mem->z, &d, mem->n) == 0)
 		return -1;
@@ -644,16 +672,19 @@ bytes_to_double(struct Mem *mem)
 static inline int
 double_to_int(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	double d = mem->u.r;
 	if (d < 0 && d >= (double)INT64_MIN) {
 		mem->u.i = (int64_t)d;
-		mem->flags = MEM_Int;
+		mem->type = MEM_TYPE_INT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_INTEGER;
 		return 0;
 	}
 	if (d >= 0 && d < (double)UINT64_MAX) {
 		mem->u.u = (uint64_t)d;
-		mem->flags = MEM_UInt;
+		mem->type = MEM_TYPE_UINT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_UNSIGNED;
 		return 0;
 	}
@@ -663,16 +694,19 @@ double_to_int(struct Mem *mem)
 static inline int
 double_to_int_precise(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	double d = mem->u.r;
 	if (d < 0 && d >= (double)INT64_MIN && (double)(int64_t)d == d) {
 		mem->u.i = (int64_t)d;
-		mem->flags = MEM_Int;
+		mem->type = MEM_TYPE_INT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_INTEGER;
 		return 0;
 	}
 	if (d >= 0 && d < (double)UINT64_MAX && (double)(uint64_t)d == d) {
 		mem->u.u = (uint64_t)d;
-		mem->flags = MEM_UInt;
+		mem->type = MEM_TYPE_UINT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_UNSIGNED;
 		return 0;
 	}
@@ -682,10 +716,12 @@ double_to_int_precise(struct Mem *mem)
 static inline int
 double_to_uint(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	double d = mem->u.r;
 	if (d >= 0 && d < (double)UINT64_MAX) {
 		mem->u.u = (uint64_t)d;
-		mem->flags = MEM_UInt;
+		mem->type = MEM_TYPE_UINT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_UNSIGNED;
 		return 0;
 	}
@@ -695,10 +731,12 @@ double_to_uint(struct Mem *mem)
 static inline int
 double_to_uint_precise(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	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->type = MEM_TYPE_UINT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_UNSIGNED;
 		return 0;
 	}
@@ -708,11 +746,13 @@ double_to_uint_precise(struct Mem *mem)
 static inline int
 double_to_str0(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	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->type = MEM_TYPE_STR;
+	mem->flags = MEM_Term;
 	mem->field_type = FIELD_TYPE_STRING;
 	return 0;
 }
@@ -720,8 +760,10 @@ double_to_str0(struct Mem *mem)
 static inline int
 double_to_bool(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	mem->u.b = mem->u.r != 0.;
-	mem->flags = MEM_Bool;
+	mem->type = MEM_TYPE_BOOL;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_BOOLEAN;
 	return 0;
 }
@@ -729,8 +771,10 @@ double_to_bool(struct Mem *mem)
 static inline int
 bool_to_int(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_BOOL);
 	mem->u.u = (uint64_t)mem->u.b;
-	mem->flags = MEM_UInt;
+	mem->type = MEM_TYPE_UINT;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_UNSIGNED;
 	return 0;
 }
@@ -738,6 +782,7 @@ bool_to_int(struct Mem *mem)
 static inline int
 bool_to_str0(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_BOOL);
 	const char *str = mem->u.b ? "TRUE" : "FALSE";
 	return mem_copy_str0(mem, str);
 }
@@ -745,6 +790,7 @@ bool_to_str0(struct Mem *mem)
 static inline int
 array_to_str0(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_ARRAY);
 	const char *str = mp_str(mem->z);
 	return mem_copy_str0(mem, str);
 }
@@ -752,6 +798,7 @@ array_to_str0(struct Mem *mem)
 static inline int
 map_to_str0(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_MAP);
 	const char *str = mp_str(mem->z);
 	return mem_copy_str0(mem, str);
 }
@@ -759,14 +806,15 @@ map_to_str0(struct Mem *mem)
 int
 mem_to_int(struct Mem *mem)
 {
-	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
-	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+	assert(mem->type < MEM_TYPE_INVALID);
+	enum mem_type type = mem->type;
+	if (type == MEM_TYPE_INT || type == MEM_TYPE_UINT)
 		return 0;
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+	if (type == MEM_TYPE_STR || type == MEM_TYPE_BIN)
 		return bytes_to_int(mem);
-	if ((mem->flags & MEM_Real) != 0)
+	if (type == MEM_TYPE_DOUBLE)
 		return double_to_int(mem);
-	if ((mem->flags & MEM_Bool) != 0)
+	if (type == MEM_TYPE_BOOL)
 		return bool_to_int(mem);
 	return -1;
 }
@@ -774,12 +822,13 @@ mem_to_int(struct Mem *mem)
 int
 mem_to_int_precise(struct Mem *mem)
 {
-	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
-	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+	assert(mem->type < MEM_TYPE_INVALID);
+	enum mem_type type = mem->type;
+	if (type == MEM_TYPE_INT || type == MEM_TYPE_UINT)
 		return 0;
-	if ((mem->flags & MEM_Str) != 0)
+	if (type == MEM_TYPE_STR)
 		return bytes_to_int(mem);
-	if ((mem->flags & MEM_Real) != 0)
+	if (type == MEM_TYPE_DOUBLE)
 		return double_to_int_precise(mem);
 	return -1;
 }
@@ -787,12 +836,13 @@ mem_to_int_precise(struct Mem *mem)
 int
 mem_to_double(struct Mem *mem)
 {
-	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
-	if ((mem->flags & MEM_Real) != 0)
+	assert(mem->type < MEM_TYPE_INVALID);
+	enum mem_type type = mem->type;
+	if (type == MEM_TYPE_DOUBLE)
 		return 0;
-	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+	if (type == MEM_TYPE_INT || type == MEM_TYPE_UINT)
 		return int_to_double(mem);
-	if ((mem->flags & MEM_Str) != 0)
+	if (type == MEM_TYPE_STR)
 		return bytes_to_double(mem);
 	return -1;
 }
@@ -800,12 +850,12 @@ 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)
+	assert(mem->type < MEM_TYPE_INVALID);
+	if (mem_is_num(mem))
 		return 0;
-	if ((mem->flags & MEM_Bool) != 0)
+	if (mem->type == MEM_TYPE_BOOL)
 		return bool_to_int(mem);
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
+	if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN) {
 		if (bytes_to_int(mem) == 0)
 			return 0;
 		return bytes_to_double(mem);
@@ -816,72 +866,77 @@ mem_to_number(struct Mem *mem)
 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)
+	assert(mem->type < MEM_TYPE_INVALID);
+	switch (mem->type) {
+	case MEM_TYPE_STR:
+		if ((mem->flags & MEM_Term) != 0)
+			return 0;
 		return str_to_str0(mem);
-	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+	case MEM_TYPE_INT:
+	case MEM_TYPE_UINT:
 		return int_to_str0(mem);
-	if ((mem->flags & MEM_Real) != 0)
+	case MEM_TYPE_DOUBLE:
 		return double_to_str0(mem);
-	if ((mem->flags & MEM_Bool) != 0)
+	case MEM_TYPE_BOOL:
 		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);
+	case MEM_TYPE_BIN:
+		return bin_to_str0(mem);
+	case MEM_TYPE_MAP:
+		return map_to_str0(mem);
+	case MEM_TYPE_ARRAY:
 		return array_to_str0(mem);
+	default:
+		return -1;
 	}
-	return -1;
 }
 
 int
 mem_to_str(struct Mem *mem)
 {
-	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
-	if ((mem->flags & MEM_Str) != 0)
+	assert(mem->type < MEM_TYPE_INVALID);
+	switch (mem->type) {
+	case MEM_TYPE_STR:
 		return 0;
-	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+	case MEM_TYPE_INT:
+	case MEM_TYPE_UINT:
 		return int_to_str0(mem);
-	if ((mem->flags & MEM_Real) != 0)
+	case MEM_TYPE_DOUBLE:
 		return double_to_str0(mem);
-	if ((mem->flags & MEM_Bool) != 0)
+	case MEM_TYPE_BOOL:
 		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);
+	case MEM_TYPE_BIN:
+		return bin_to_str(mem);
+	case MEM_TYPE_MAP:
+		return map_to_str0(mem);
+	case MEM_TYPE_ARRAY:
 		return array_to_str0(mem);
+	default:
+		return -1;
 	}
-	return -1;
 }
 
 int
 mem_cast_explicit(struct Mem *mem, enum field_type type)
 {
-	if ((mem->flags & MEM_Null) != 0) {
+	if (mem->type == MEM_TYPE_NULL) {
 		mem->field_type = type;
 		return 0;
 	}
 	switch (type) {
 	case FIELD_TYPE_UNSIGNED:
-		if ((mem->flags & MEM_UInt) != 0)
+		switch (mem->type) {
+		case MEM_TYPE_UINT:
 			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)
+		case MEM_TYPE_STR:
+		case MEM_TYPE_BIN:
 			return bytes_to_uint(mem);
-		if ((mem->flags & MEM_Real) != 0)
+		case MEM_TYPE_DOUBLE:
 			return double_to_int(mem);
-		if ((mem->flags & MEM_Bool) != 0)
+		case MEM_TYPE_BOOL:
 			return bool_to_int(mem);
-		return -1;
+		default:
+			return -1;
+		}
 	case FIELD_TYPE_STRING:
 		return mem_to_str(mem);
 	case FIELD_TYPE_DOUBLE:
@@ -889,26 +944,29 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
 	case FIELD_TYPE_INTEGER:
 		return mem_to_int(mem);
 	case FIELD_TYPE_BOOLEAN:
-		if ((mem->flags & MEM_Bool) != 0)
+		switch (mem->type) {
+		case MEM_TYPE_BOOL:
 			return 0;
-		if ((mem->flags & (MEM_UInt | MEM_Int)) != 0)
+		case MEM_TYPE_INT:
+		case MEM_TYPE_UINT:
 			return int_to_bool(mem);
-		if ((mem->flags & MEM_Str) != 0)
+		case MEM_TYPE_STR:
 			return str_to_bool(mem);
-		if ((mem->flags & MEM_Real) != 0)
+		case MEM_TYPE_DOUBLE:
 			return double_to_bool(mem);
-		return -1;
+		default:
+			return -1;
+		}
 	case FIELD_TYPE_VARBINARY:
-		if ((mem->flags & MEM_Blob) != 0)
-			return 0;
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return str_to_bin(mem);
+		if (mem_is_bytes(mem))
+			return 0;
 		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)
+		if (mem->type == MEM_TYPE_MAP || mem->type == MEM_TYPE_ARRAY)
 			return -1;
 		return 0;
 	default:
@@ -920,56 +978,55 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
 int
 mem_cast_implicit(struct Mem *mem, enum field_type type)
 {
-	if ((mem->flags & MEM_Null) != 0) {
+	if (mem->type == MEM_TYPE_NULL) {
 		mem->field_type = type;
 		return 0;
 	}
 	switch (type) {
 	case FIELD_TYPE_UNSIGNED:
-		if ((mem->flags & MEM_UInt) != 0)
+		if (mem->type == MEM_TYPE_UINT)
 			return 0;
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_uint(mem);
 		return -1;
 	case FIELD_TYPE_STRING:
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return 0;
 		return -1;
 	case FIELD_TYPE_DOUBLE:
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return 0;
-		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
 			return int_to_double(mem);
 		return -1;
 	case FIELD_TYPE_INTEGER:
-		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
 			return 0;
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_int(mem);
 		return -1;
 	case FIELD_TYPE_BOOLEAN:
-		if ((mem->flags & MEM_Bool) != 0)
+		if (mem->type == MEM_TYPE_BOOL)
 			return 0;
 		return -1;
 	case FIELD_TYPE_VARBINARY:
-		if ((mem->flags & MEM_Blob) != 0)
+		if (mem->type != MEM_TYPE_STR && mem_is_bytes(mem))
 			return 0;
 		return -1;
 	case FIELD_TYPE_NUMBER:
-		if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
+		if (mem_is_num(mem))
 			return 0;
 		return -1;
 	case FIELD_TYPE_MAP:
-		if (mem_is_map(mem))
+		if (mem->type == MEM_TYPE_MAP)
 			return 0;
 		return -1;
 	case FIELD_TYPE_ARRAY:
-		if (mem_is_array(mem))
+		if (mem->type == MEM_TYPE_ARRAY)
 			return 0;
 		return -1;
 	case FIELD_TYPE_SCALAR:
-		if ((mem->flags & MEM_Blob) != 0 &&
-		    (mem->flags & MEM_Subtype) != 0)
+		if (mem->type == MEM_TYPE_MAP || mem->type == MEM_TYPE_ARRAY)
 			return -1;
 		return 0;
 	case FIELD_TYPE_ANY:
@@ -983,66 +1040,65 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
 int
 mem_cast_implicit_old(struct Mem *mem, enum field_type type)
 {
-	if (mem_is_null(mem))
+	if (mem->type == MEM_TYPE_NULL)
 		return 0;
 	switch (type) {
 	case FIELD_TYPE_UNSIGNED:
-		if ((mem->flags & MEM_UInt) != 0)
+		if (mem->type == MEM_TYPE_UINT)
 			return 0;
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_uint_precise(mem);
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return bytes_to_uint(mem);
 		return -1;
 	case FIELD_TYPE_STRING:
-		if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+		if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN)
 			return 0;
-		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
 			return int_to_str0(mem);
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_str0(mem);
 		return -1;
 	case FIELD_TYPE_DOUBLE:
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return 0;
-		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
 			return int_to_double(mem);
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return bin_to_str(mem);
 		return -1;
 	case FIELD_TYPE_INTEGER:
-		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
 			return 0;
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return bytes_to_int(mem);
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_int_precise(mem);
 		return -1;
 	case FIELD_TYPE_BOOLEAN:
-		if ((mem->flags & MEM_Bool) != 0)
+		if (mem->type == MEM_TYPE_BOOL)
 			return 0;
 		return -1;
 	case FIELD_TYPE_VARBINARY:
-		if ((mem->flags & MEM_Blob) != 0)
+		if (mem->type == MEM_TYPE_BIN)
 			return 0;
 		return -1;
 	case FIELD_TYPE_NUMBER:
-		if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
+		if (mem_is_num(mem))
 			return 0;
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return mem_to_number(mem);
 		return -1;
 	case FIELD_TYPE_MAP:
-		if (mem_is_map(mem))
+		if (mem->type == MEM_TYPE_MAP)
 			return 0;
 		return -1;
 	case FIELD_TYPE_ARRAY:
-		if (mem_is_array(mem))
+		if (mem->type == MEM_TYPE_ARRAY)
 			return 0;
 		return -1;
 	case FIELD_TYPE_SCALAR:
-		if ((mem->flags & MEM_Blob) != 0 &&
-		    (mem->flags & MEM_Subtype) != 0)
+		if (mem->type == MEM_TYPE_MAP || mem->type == MEM_TYPE_ARRAY)
 			return -1;
 		return 0;
 	default:
@@ -1054,19 +1110,19 @@ 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)
 {
-	if ((mem->flags & MEM_Int) != 0) {
+	if (mem->type == MEM_TYPE_INT) {
 		*i = mem->u.i;
 		*is_neg = true;
 		return 0;
 	}
-	if ((mem->flags & MEM_UInt) != 0) {
+	if (mem->type == MEM_TYPE_UINT) {
 		*i = mem->u.i;
 		*is_neg = false;
 		return 0;
 	}
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+	if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN)
 		return sql_atoi64(mem->z, i, is_neg, mem->n);
-	if ((mem->flags & MEM_Real) != 0) {
+	if (mem->type == MEM_TYPE_DOUBLE) {
 		double d = mem->u.r;
 		if (d < 0 && d >= (double)INT64_MIN) {
 			*i = (int64_t)d;
@@ -1086,20 +1142,20 @@ mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
 int
 mem_get_uint(const struct Mem *mem, uint64_t *u)
 {
-	if ((mem->flags & MEM_Int) != 0)
+	if (mem->type == MEM_TYPE_INT)
 		return -1;
-	if ((mem->flags & MEM_UInt) != 0) {
+	if (mem->type == MEM_TYPE_UINT) {
 		*u = mem->u.u;
 		return 0;
 	}
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
+	if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN) {
 		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) {
+	if (mem->type == MEM_TYPE_DOUBLE) {
 		double d = mem->u.r;
 		if (d >= 0 && d < (double)UINT64_MAX) {
 			*u = (uint64_t)d;
@@ -1113,19 +1169,19 @@ mem_get_uint(const struct Mem *mem, uint64_t *u)
 int
 mem_get_double(const struct Mem *mem, double *d)
 {
-	if ((mem->flags & MEM_Real) != 0) {
+	if (mem->type == MEM_TYPE_DOUBLE) {
 		*d = mem->u.r;
 		return 0;
 	}
-	if ((mem->flags & MEM_Int) != 0) {
+	if (mem->type == MEM_TYPE_INT) {
 		*d = (double)mem->u.i;
 		return 0;
 	}
-	if ((mem->flags & MEM_UInt) != 0) {
+	if (mem->type == MEM_TYPE_UINT) {
 		*d = (double)mem->u.u;
 		return 0;
 	}
-	if ((mem->flags & MEM_Str) != 0) {
+	if (mem->type == MEM_TYPE_STR) {
 		if (sqlAtoF(mem->z, d, mem->n) == 0)
 			return -1;
 		return 0;
@@ -1136,7 +1192,7 @@ mem_get_double(const struct Mem *mem, double *d)
 int
 mem_get_bool(const struct Mem *mem, bool *b)
 {
-	if ((mem->flags & MEM_Bool) != 0) {
+	if (mem->type == MEM_TYPE_BOOL) {
 		*b = mem->u.b;
 		return 0;
 	}
@@ -1146,7 +1202,7 @@ mem_get_bool(const struct Mem *mem, bool *b)
 int
 mem_get_str0(const struct Mem *mem, const char **s)
 {
-	if ((mem->flags & MEM_Str) == 0 || (mem->flags & MEM_Term) == 0)
+	if (mem->type != MEM_TYPE_STR || (mem->flags & MEM_Term) == 0)
 		return -1;
 	*s = mem->z;
 	return 0;
@@ -1155,11 +1211,11 @@ mem_get_str0(const struct Mem *mem, const char **s)
 int
 mem_get_bin(const struct Mem *mem, const char **s)
 {
-	if ((mem->flags & MEM_Str) != 0) {
+	if (mem->type == MEM_TYPE_STR) {
 		*s = mem->n > 0 ? mem->z : NULL;
 		return 0;
 	}
-	if ((mem->flags & MEM_Blob) == 0 || (mem->flags & MEM_Zero) != 0)
+	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
 		return -1;
 	*s = mem->z;
 	return 0;
@@ -1168,9 +1224,9 @@ mem_get_bin(const struct Mem *mem, const char **s)
 int
 mem_len(const struct Mem *mem, uint32_t *len)
 {
-	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
+	if (!mem_is_bytes(mem))
 		return -1;
-	if ((mem->flags & MEM_Blob) !=0 && (mem->flags & MEM_Zero) != 0)
+	if (mem->type == MEM_TYPE_BIN && (mem->flags & MEM_Zero) != 0)
 		*len = mem->n + mem->u.nZero;
 	else
 		*len = mem->n;
@@ -1180,7 +1236,7 @@ mem_len(const struct Mem *mem, uint32_t *len)
 int
 mem_get_agg(const struct Mem *mem, void **accum)
 {
-	if ((mem->flags & MEM_Agg) == 0)
+	if (mem->type != MEM_TYPE_AGG)
 		return -1;
 	*accum = mem->z;
 	return 0;
@@ -1191,16 +1247,17 @@ mem_copy(struct Mem *to, const struct Mem *from)
 {
 	mem_clear(to);
 	to->u = from->u;
+	to->type = from->type;
 	to->flags = from->flags;
-	to->subtype = from->subtype;
 	to->field_type = from->field_type;
 	to->n = from->n;
 	to->z = from->z;
-	if ((to->flags & (MEM_Str | MEM_Blob)) == 0)
+	if (!mem_is_bytes(to))
 		return 0;
 	if ((to->flags & MEM_Static) != 0)
 		return 0;
-	if ((to->flags & (MEM_Zero | MEM_Blob)) == (MEM_Zero | MEM_Blob))
+	assert((to->flags & MEM_Zero) == 0 || to->type == MEM_TYPE_BIN);
+	if ((to->flags & MEM_Zero) != 0)
 		return sqlVdbeMemExpandBlob(to);
 	to->zMalloc = sqlDbReallocOrFree(to->db, to->zMalloc, to->n);
 	if (to->zMalloc == NULL)
@@ -1208,7 +1265,7 @@ mem_copy(struct Mem *to, const struct Mem *from)
 	to->szMalloc = sqlDbMallocSize(to->db, to->zMalloc);
 	memcpy(to->zMalloc, to->z, to->n);
 	to->z = to->zMalloc;
-	to->flags &= (MEM_Str | MEM_Blob | MEM_Term | MEM_Subtype);
+	to->flags &= MEM_Term;
 	return 0;
 }
 
@@ -1217,16 +1274,16 @@ mem_copy_as_ephemeral(struct Mem *to, const struct Mem *from)
 {
 	mem_clear(to);
 	to->u = from->u;
+	to->type = from->type;
 	to->flags = from->flags;
-	to->subtype = from->subtype;
 	to->field_type = from->field_type;
 	to->n = from->n;
 	to->z = from->z;
-	if ((to->flags & (MEM_Str | MEM_Blob)) == 0)
+	if (!mem_is_bytes(to))
 		return;
 	if ((to->flags & (MEM_Static | MEM_Ephem)) != 0)
 		return;
-	to->flags &= (MEM_Str | MEM_Blob | MEM_Term | MEM_Zero | MEM_Subtype);
+	to->flags &= MEM_Term | MEM_Zero;
 	to->flags |= MEM_Ephem;
 	return;
 }
@@ -1236,7 +1293,8 @@ mem_move(struct Mem *to, struct Mem *from)
 {
 	mem_destroy(to);
 	memcpy(to, from, sizeof(*to));
-	from->flags = MEM_Null;
+	from->type = MEM_TYPE_NULL;
+	from->flags = 0;
 	from->szMalloc = 0;
 	from->zMalloc = NULL;
 }
@@ -1247,7 +1305,7 @@ try_return_null(const struct Mem *a, const struct Mem *b, struct Mem *result,
 {
 	mem_clear(result);
 	result->field_type = type;
-	return (((a->flags | b->flags) & MEM_Null) != 0);
+	return a->type == MEM_TYPE_NULL || b->type == MEM_TYPE_NULL;
 }
 
 int
@@ -1258,7 +1316,7 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 		if (try_return_null(a, b, result, FIELD_TYPE_STRING))
 			return 0;
 	} else {
-		if (((a->flags | b->flags) & MEM_Null) != 0) {
+		if (a->type == MEM_TYPE_NULL || b->type == MEM_TYPE_NULL) {
 			mem_clear(a);
 			result->field_type = FIELD_TYPE_STRING;
 			return 0;
@@ -1266,19 +1324,18 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 	}
 
 	/* Concatenation operation can be applied only to strings and blobs. */
-	if ((b->flags & (MEM_Str | MEM_Blob)) == 0) {
+	if (!mem_is_bytes(b)) {
 		diag_set(ClientError, ER_INCONSISTENT_TYPES,
 			 "text or varbinary", mem_type_to_str(b));
 		return -1;
 	}
-	if ((a->flags & (MEM_Str | MEM_Blob)) == 0) {
+	if (!mem_is_bytes(a)) {
 		diag_set(ClientError, ER_INCONSISTENT_TYPES,
 			 "text or varbinary", mem_type_to_str(a));
 		return -1;
 	}
 
-	/* Moreover, both operands must be of the same type. */
-	if ((b->flags & MEM_Str) != (a->flags & MEM_Str)) {
+	if (b->type != a->type && ((b->type | a->type) & MEM_TYPE_STR) != 0) {
 		diag_set(ClientError, ER_INCONSISTENT_TYPES,
 			 mem_type_to_str(a), mem_type_to_str(b));
 		return -1;
@@ -1295,8 +1352,9 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 	if (sqlVdbeMemGrow(result, size, result == a) != 0)
 		return -1;
 
-	result->flags = a->flags & (MEM_Str | MEM_Blob);
-	if ((result->flags & MEM_Blob) != 0)
+	result->type = a->type == MEM_TYPE_STR ? MEM_TYPE_STR : MEM_TYPE_BIN;
+	result->flags = 0;
+	if (result->type == MEM_TYPE_BIN)
 		result->field_type = FIELD_TYPE_VARBINARY;
 	if (result != a)
 		memcpy(result->z, a->z, a->n);
@@ -1311,36 +1369,34 @@ struct sql_num {
 		uint64_t u;
 		double d;
 	};
-	int type;
+	enum mem_type type;
 	bool is_neg;
 };
 
 static int
 get_number(const struct Mem *mem, struct sql_num *number)
 {
-	if ((mem->flags & MEM_Real) != 0) {
+	if (mem->type == MEM_TYPE_DOUBLE) {
 		number->d = mem->u.r;
-		number->type = MEM_Real;
+		number->type = MEM_TYPE_DOUBLE;
 		return 0;
 	}
-	if ((mem->flags & MEM_Int) != 0) {
+	if (mem->type == MEM_TYPE_INT) {
 		number->i = mem->u.i;
-		number->type = MEM_Int;
+		number->type = MEM_TYPE_INT;
 		number->is_neg = true;
 		return 0;
 	}
-	if ((mem->flags & MEM_UInt) != 0) {
+	if (mem->type == MEM_TYPE_UINT) {
 		number->u = mem->u.u;
-		number->type = MEM_UInt;
+		number->type = MEM_TYPE_UINT;
 		number->is_neg = false;
 		return 0;
 	}
-	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
-		return -1;
-	if ((mem->flags & MEM_Subtype) != 0)
+	if (mem->type != MEM_TYPE_STR && mem->type != MEM_TYPE_BIN)
 		return -1;
 	if (sql_atoi64(mem->z, &number->i, &number->is_neg, mem->n) == 0) {
-		number->type = number->is_neg ? MEM_Int : MEM_UInt;
+		number->type = number->is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
 		/*
 		 * The next line should be removed along with the is_neg field
 		 * of struct sql_num. The integer type tells us about the sign.
@@ -1351,7 +1407,7 @@ get_number(const struct Mem *mem, struct sql_num *number)
 		return 0;
 	}
 	if (sqlAtoF(mem->z, &number->d, mem->n) != 0) {
-		number->type = MEM_Real;
+		number->type = MEM_TYPE_DOUBLE;
 		return 0;
 	}
 	return -1;
@@ -1372,14 +1428,14 @@ arithmetic_prepare(const struct Mem *left, const struct Mem *right,
 		return -1;
 	}
 	assert(a->type != 0 && b->type != 0);
-	if (a->type == MEM_Real && b->type != MEM_Real) {
-		b->d = b->type == MEM_Int ? (double)b->i : (double)b->u;
-		b->type = MEM_Real;
+	if (a->type == MEM_TYPE_DOUBLE && b->type != MEM_TYPE_DOUBLE) {
+		b->d = b->type == MEM_TYPE_INT ? (double)b->i : (double)b->u;
+		b->type = MEM_TYPE_DOUBLE;
 		return 0;
 	}
-	if (a->type != MEM_Real && b->type == MEM_Real) {
-		a->d = a->type == MEM_Int ? (double)a->i : (double)a->u;
-		a->type = MEM_Real;
+	if (a->type != MEM_TYPE_DOUBLE && b->type == MEM_TYPE_DOUBLE) {
+		a->d = a->type == MEM_TYPE_INT ? (double)a->i : (double)a->u;
+		a->type = MEM_TYPE_DOUBLE;
 		return 0;
 	}
 	return 0;
@@ -1395,10 +1451,11 @@ mem_add(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (arithmetic_prepare(left, right, &a, &b) != 0)
 		return -1;
 
-	assert(a.type != MEM_Real || a.type == b.type);
-	if (a.type == MEM_Real) {
+	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
+	if (a.type == MEM_TYPE_DOUBLE) {
 		result->u.r = a.d + b.d;
-		result->flags = MEM_Real;
+		result->type = MEM_TYPE_DOUBLE;
+		assert(result->flags == 0);
 		return 0;
 	}
 
@@ -1409,7 +1466,8 @@ mem_add(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		return -1;
 	}
 	result->u.i = res;
-	result->flags = is_neg ? MEM_Int : MEM_UInt;
+	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1423,10 +1481,11 @@ mem_sub(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (arithmetic_prepare(left, right, &a, &b) != 0)
 		return -1;
 
-	assert(a.type != MEM_Real || a.type == b.type);
-	if (a.type == MEM_Real) {
+	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
+	if (a.type == MEM_TYPE_DOUBLE) {
 		result->u.r = a.d - b.d;
-		result->flags = MEM_Real;
+		result->type = MEM_TYPE_DOUBLE;
+		assert(result->flags == 0);
 		return 0;
 	}
 
@@ -1437,7 +1496,8 @@ mem_sub(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		return -1;
 	}
 	result->u.i = res;
-	result->flags = is_neg ? MEM_Int : MEM_UInt;
+	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1451,10 +1511,11 @@ mem_mul(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (arithmetic_prepare(left, right, &a, &b) != 0)
 		return -1;
 
-	assert(a.type != MEM_Real || a.type == b.type);
-	if (a.type == MEM_Real) {
+	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
+	if (a.type == MEM_TYPE_DOUBLE) {
 		result->u.r = a.d * b.d;
-		result->flags = MEM_Real;
+		result->type = MEM_TYPE_DOUBLE;
+		assert(result->flags == 0);
 		return 0;
 	}
 
@@ -1465,7 +1526,8 @@ mem_mul(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		return -1;
 	}
 	result->u.i = res;
-	result->flags = is_neg ? MEM_Int : MEM_UInt;
+	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1479,15 +1541,16 @@ mem_div(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (arithmetic_prepare(left, right, &a, &b) != 0)
 		return -1;
 
-	assert(a.type != MEM_Real || a.type == b.type);
-	if (a.type == MEM_Real) {
+	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
+	if (a.type == MEM_TYPE_DOUBLE) {
 		if (b.d == 0.) {
 			diag_set(ClientError, ER_SQL_EXECUTE,
 				 "division by zero");
 			return -1;
 		}
 		result->u.r = a.d / b.d;
-		result->flags = MEM_Real;
+		result->type = MEM_TYPE_DOUBLE;
+		assert(result->flags == 0);
 		return 0;
 	}
 
@@ -1502,7 +1565,8 @@ mem_div(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		return -1;
 	}
 	result->u.i = res;
-	result->flags = is_neg ? MEM_Int : MEM_UInt;
+	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1516,14 +1580,14 @@ mem_rem(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (arithmetic_prepare(left, right, &a, &b) != 0)
 		return -1;
 
-	assert(a.type != MEM_Real || a.type == b.type);
+	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
 	/*
 	 * TODO: This operation works wrong when double d > INT64_MAX and
 	 * d < UINT64_MAX. Also, there may be precision losses due to
 	 * conversion integer to double and back.
 	 */
-	a.i = a.type == MEM_Real ? (int64_t)a.d : a.i;
-	b.i = b.type == MEM_Real ? (int64_t)b.d : b.i;
+	a.i = a.type == MEM_TYPE_DOUBLE ? (int64_t)a.d : a.i;
+	b.i = b.type == MEM_TYPE_DOUBLE ? (int64_t)b.d : b.i;
 	if (b.i == 0) {
 		diag_set(ClientError, ER_SQL_EXECUTE, "division by zero");
 		return -1;
@@ -1535,7 +1599,8 @@ mem_rem(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		return -1;
 	}
 	result->u.i = res;
-	result->flags = is_neg ? MEM_Int : MEM_UInt;
+	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1567,7 +1632,8 @@ mem_bit_and(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (bitwise_prepare(left, right, &a, &b) != 0)
 		return -1;
 	result->u.i = a & b;
-	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
+	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1581,7 +1647,8 @@ mem_bit_or(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (bitwise_prepare(left, right, &a, &b) != 0)
 		return -1;
 	result->u.i = a | b;
-	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
+	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1603,7 +1670,8 @@ mem_shift_left(const struct Mem *left, const struct Mem *right,
 		result->u.i = 0;
 	else
 		result->u.i = a << b;
-	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
+	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1625,7 +1693,8 @@ mem_shift_right(const struct Mem *left, const struct Mem *right,
 		result->u.i = a >= 0 ? 0 : -1;
 	else
 		result->u.i = a >> b;
-	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
+	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1634,7 +1703,7 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
 {
 	mem_clear(result);
 	result->field_type = FIELD_TYPE_INTEGER;
-	if ((mem->flags & MEM_Null) != 0)
+	if (mem->type == MEM_TYPE_NULL)
 		return 0;
 	int64_t i;
 	bool unused;
@@ -1644,14 +1713,15 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
 		return -1;
 	}
 	result->u.i = ~i;
-	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
+	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
 int
 mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
 {
-	if ((a->flags & b->flags & MEM_Bool) == 0)
+	if (a->type != MEM_TYPE_BOOL || b->type != MEM_TYPE_BOOL)
 		return -1;
 	if (a->u.b == b->u.b)
 		*result = 0;
@@ -1665,7 +1735,7 @@ mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
 int
 mem_cmp_bin(const struct Mem *a, const struct Mem *b, int *result)
 {
-	if ((a->flags & b->flags & MEM_Blob) == 0)
+	if (!mem_is_bin_ext(a) || !mem_is_bin_ext(b))
 		return -1;
 	int an = a->n;
 	int bn = b->n;
@@ -1722,8 +1792,8 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
 	}
 	if (get_number(left, &a) != 0)
 		return -1;
-	if (a.type == MEM_Real) {
-		if (b.type == MEM_Real) {
+	if (a.type == MEM_TYPE_DOUBLE) {
+		if (b.type == MEM_TYPE_DOUBLE) {
 			if (a.d > b.d)
 				*result = 1;
 			else if (a.d < b.d)
@@ -1732,14 +1802,14 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
 				*result = 0;
 			return 0;
 		}
-		if (b.type == MEM_Int)
+		if (b.type == MEM_TYPE_INT)
 			*result = double_compare_nint64(a.d, b.i, 1);
 		else
 			*result = double_compare_uint64(a.d, b.u, 1);
 		return 0;
 	}
-	if (a.type == MEM_Int) {
-		if (b.type == MEM_Int) {
+	if (a.type == MEM_TYPE_INT) {
+		if (b.type == MEM_TYPE_INT) {
 			if (a.i > b.i)
 				*result = 1;
 			else if (a.i < b.i)
@@ -1748,14 +1818,14 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
 				*result = 0;
 			return 0;
 		}
-		if (b.type == MEM_UInt)
+		if (b.type == MEM_TYPE_UINT)
 			*result = -1;
 		else
 			*result = double_compare_nint64(b.d, a.i, -1);
 		return 0;
 	}
-	assert(a.type == MEM_UInt);
-	if (b.type == MEM_UInt) {
+	assert(a.type == MEM_TYPE_UINT);
+	if (b.type == MEM_TYPE_UINT) {
 		if (a.u > b.u)
 			*result = 1;
 		else if (a.u < b.u)
@@ -1764,7 +1834,7 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
 			*result = 0;
 		return 0;
 	}
-	if (b.type == MEM_Int)
+	if (b.type == MEM_TYPE_INT)
 		*result = 1;
 	else
 		*result = double_compare_uint64(b.d, a.u, -1);
@@ -1778,15 +1848,15 @@ mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
 	char *a;
 	uint32_t an;
 	char bufl[BUF_SIZE];
-	if ((left->flags & MEM_Str) != 0) {
+	if (left->type == MEM_TYPE_STR) {
 		a = left->z;
 		an = left->n;
 	} else {
-		assert((left->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0);
+		assert(mem_is_num(left));
 		a = &bufl[0];
-		if ((left->flags & MEM_Int) != 0)
+		if (left->type == MEM_TYPE_INT)
 			sql_snprintf(BUF_SIZE, a, "%lld", left->u.i);
-		else if ((left->flags & MEM_UInt) != 0)
+		else if (left->type == MEM_TYPE_UINT)
 			sql_snprintf(BUF_SIZE, a, "%llu", left->u.u);
 		else
 			sql_snprintf(BUF_SIZE, a, "%!.15g", left->u.r);
@@ -1796,15 +1866,15 @@ mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
 	char *b;
 	uint32_t bn;
 	char bufr[BUF_SIZE];
-	if ((right->flags & MEM_Str) != 0) {
+	if (right->type == MEM_TYPE_STR) {
 		b = right->z;
 		bn = right->n;
 	} else {
-		assert((right->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0);
+		assert(mem_is_num(right));
 		b = &bufr[0];
-		if ((right->flags & MEM_Int) != 0)
+		if (right->type == MEM_TYPE_INT)
 			sql_snprintf(BUF_SIZE, b, "%lld", right->u.i);
-		else if ((right->flags & MEM_UInt) != 0)
+		else if (right->type == MEM_TYPE_UINT)
 			sql_snprintf(BUF_SIZE, b, "%llu", right->u.u);
 		else
 			sql_snprintf(BUF_SIZE, b, "%!.15g", right->u.r);
@@ -1822,13 +1892,6 @@ mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
 	return 0;
 }
 
-static inline bool
-mem_has_msgpack_subtype(struct Mem *mem)
-{
-	return (mem->flags & MEM_Subtype) != 0 &&
-	       mem->subtype == SQL_SUBTYPE_MSGPACK;
-}
-
 /*
  * Both *pMem1 and *pMem2 contain string values. Compare the two values
  * using the collation sequence pColl. As usual, return a negative , zero
@@ -1864,20 +1927,22 @@ char *
 mem_type_to_str(const struct Mem *p)
 {
 	assert(p != NULL);
-	switch (p->flags & MEM_PURE_TYPE_MASK) {
-	case MEM_Null:
+	switch (p->type) {
+	case MEM_TYPE_NULL:
 		return "NULL";
-	case MEM_Str:
+	case MEM_TYPE_STR:
 		return "text";
-	case MEM_Int:
+	case MEM_TYPE_INT:
 		return "integer";
-	case MEM_UInt:
+	case MEM_TYPE_UINT:
 		return "unsigned";
-	case MEM_Real:
+	case MEM_TYPE_DOUBLE:
 		return "real";
-	case MEM_Blob:
+	case MEM_TYPE_ARRAY:
+	case MEM_TYPE_MAP:
+	case MEM_TYPE_BIN:
 		return "varbinary";
-	case MEM_Bool:
+	case MEM_TYPE_BOOL:
 		return "boolean";
 	default:
 		unreachable();
@@ -1887,28 +1952,32 @@ mem_type_to_str(const struct Mem *p)
 enum mp_type
 mem_mp_type(struct Mem *mem)
 {
-	switch (mem->flags & MEM_PURE_TYPE_MASK) {
-	case MEM_Int:
-		return MP_INT;
-	case MEM_UInt:
+	assert(mem->type < MEM_TYPE_INVALID);
+	switch (mem->type) {
+	case MEM_TYPE_NULL:
+		return MP_NIL;
+	case MEM_TYPE_UINT:
 		return MP_UINT;
-	case MEM_Real:
-		return MP_DOUBLE;
-	case MEM_Str:
+	case MEM_TYPE_INT:
+		return MP_INT;
+	case MEM_TYPE_STR:
 		return MP_STR;
-	case MEM_Blob:
-		if ((mem->flags & MEM_Subtype) == 0 ||
-		     mem->subtype != SQL_SUBTYPE_MSGPACK)
-			return MP_BIN;
-		assert(mp_typeof(*mem->z) == MP_MAP ||
-		       mp_typeof(*mem->z) == MP_ARRAY);
-		return mp_typeof(*mem->z);
-	case MEM_Bool:
+	case MEM_TYPE_BIN:
+		return MP_BIN;
+	case MEM_TYPE_ARRAY:
+		return MP_ARRAY;
+	case MEM_TYPE_MAP:
+		return MP_MAP;
+	case MEM_TYPE_BOOL:
 		return MP_BOOL;
-	case MEM_Null:
-		return MP_NIL;
-	default: unreachable();
+	case MEM_TYPE_FLOAT:
+		return MP_FLOAT;
+	case MEM_TYPE_DOUBLE:
+		return MP_DOUBLE;
+	default:
+		unreachable();
 	}
+	return MP_NIL;
 }
 
 /* EVIDENCE-OF: R-12793-43283 Every value in sql has one of five
@@ -1944,11 +2013,6 @@ sqlVdbeCheckMemInvariants(Mem * p)
 	 */
 	assert((p->flags & MEM_Dyn) == 0 || p->szMalloc == 0);
 
-	/* Cannot be both MEM_Int and MEM_Real at the same time */
-	assert((p->flags & (MEM_Int | MEM_Real)) != (MEM_Int | MEM_Real));
-	/* Can't be both UInt and Int at the same time.  */
-	assert((p->flags & (MEM_Int | MEM_UInt)) != (MEM_Int | MEM_UInt));
-
 	/* The szMalloc field holds the correct memory allocation size */
 	assert(p->szMalloc == 0 ||
 	       p->szMalloc == sqlDbMallocSize(p->db, p->zMalloc));
@@ -1961,7 +2025,7 @@ sqlVdbeCheckMemInvariants(Mem * p)
 	 *   (3) An ephemeral string or blob
 	 *   (4) A static string or blob
 	 */
-	if ((p->flags & (MEM_Str | MEM_Blob)) && p->n > 0) {
+	if ((p->type == MEM_TYPE_STR || p->type == MEM_TYPE_BIN) && p->n > 0) {
 		assert(((p->szMalloc > 0 && p->z == p->zMalloc) ? 1 : 0) +
 		       ((p->flags & MEM_Dyn) != 0 ? 1 : 0) +
 		       ((p->flags & MEM_Ephem) != 0 ? 1 : 0) +
@@ -1980,7 +2044,7 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
 	char *zCsr = zBuf;
 	int f = pMem->flags;
 
-	if (f&MEM_Blob) {
+	if (pMem->type == MEM_TYPE_BIN) {
 		int i;
 		char c;
 		if (f & MEM_Dyn) {
@@ -2016,7 +2080,7 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
 			zCsr += sqlStrlen30(zCsr);
 		}
 		*zCsr = '\0';
-	} else if (f & MEM_Str) {
+	} else if (pMem->type == MEM_TYPE_STR) {
 		int j, k;
 		zBuf[0] = ' ';
 		if (f & MEM_Dyn) {
@@ -2056,26 +2120,34 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
 static void
 memTracePrint(Mem *p)
 {
-	if (p->flags & MEM_Undefined) {
-		printf(" undefined");
-	} else if (p->flags & MEM_Null) {
+	switch (p->type) {
+	case MEM_TYPE_NULL:
 		printf(" NULL");
-	} else if ((p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str)) {
-		printf(" si:%lld", p->u.i);
-	} else if (p->flags & MEM_Int) {
+		return;
+	case MEM_TYPE_INT:
 		printf(" i:%lld", p->u.i);
-	} else if (p->flags & MEM_UInt) {
+		return;
+	case MEM_TYPE_UINT:
 		printf(" u:%"PRIu64"", p->u.u);
-	} else if (p->flags & MEM_Real) {
+		return;
+	case MEM_TYPE_DOUBLE:
 		printf(" r:%g", p->u.r);
-	} else if (p->flags & MEM_Bool) {
+		return;
+	case MEM_TYPE_INVALID:
+		printf(" undefined");
+		return;
+	case MEM_TYPE_BOOL:
 		printf(" bool:%s", SQL_TOKEN_BOOLEAN(p->u.b));
-	} else {
+		return;
+	default: {
 		char zBuf[200];
 		sqlVdbeMemPrettyPrint(p, zBuf);
 		printf(" %s", zBuf);
+		if (p->type == MEM_TYPE_MAP || p->type == MEM_TYPE_ARRAY)
+			printf(" subtype=0x%02x", SQL_SUBTYPE_MSGPACK);
+		return;
+	}
 	}
-	if (p->flags & MEM_Subtype) printf(" subtype=0x%02x", p->subtype);
 }
 
 void
@@ -2095,7 +2167,7 @@ sqlVdbeMemExpandBlob(Mem * pMem)
 {
 	int nByte;
 	assert(pMem->flags & MEM_Zero);
-	assert(pMem->flags & MEM_Blob);
+	assert(pMem->type == MEM_TYPE_BIN);
 
 	/* Set nByte to the number of bytes required to store the expanded blob. */
 	nByte = pMem->n + pMem->u.nZero;
@@ -2121,7 +2193,7 @@ sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
 	/* If the bPreserve flag is set to true, then the memory cell must already
 	 * contain a valid string or blob value.
 	 */
-	assert(bPreserve == 0 || pMem->flags & (MEM_Blob | MEM_Str));
+	assert(bPreserve == 0 || mem_is_bytes(pMem));
 	testcase(bPreserve && pMem->z == 0);
 
 	assert(pMem->szMalloc == 0 ||
@@ -2168,9 +2240,8 @@ sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
  * routine is a no-op.
  *
  * Any prior string or blob content in the pMem object may be discarded.
- * The pMem->xDel destructor is called, if it exists.  Though MEM_Str
- * and MEM_Blob values may be discarded, MEM_Int, MEM_Real, and MEM_Null
- * values are preserved.
+ * The pMem->xDel destructor is called, if it exists. Though STRING, VARBINARY,
+ * MAP and ARRAY values may be discarded, all other values are preserved.
  *
  * Return 0 on success or -1 if unable to complete the resizing.
  */
@@ -2184,7 +2255,6 @@ sqlVdbeMemClearAndResize(Mem * pMem, int szNew)
 	}
 	assert((pMem->flags & MEM_Dyn) == 0);
 	pMem->z = pMem->zMalloc;
-	pMem->flags &= (MEM_Null | MEM_Int | MEM_Real);
 	return 0;
 }
 
@@ -2208,7 +2278,8 @@ sqlValueNew(sql * db)
 {
 	Mem *p = sqlDbMallocZero(db, sizeof(*p));
 	if (p) {
-		p->flags = MEM_Null;
+		p->type = MEM_TYPE_NULL;
+		assert(p->flags == 0);
 		p->db = db;
 	}
 	return p;
@@ -2223,7 +2294,8 @@ releaseMemArray(Mem * p, int N)
 			assert((&p[1]) == pEnd || p[0].db == p[1].db);
 			assert(sqlVdbeCheckMemInvariants(p));
 			mem_destroy(p);
-			p->flags = MEM_Undefined;
+			p->type = MEM_TYPE_INVALID;
+			assert(p->flags == 0);
 		} while ((++p) < pEnd);
 	}
 }
@@ -2236,7 +2308,7 @@ int
 sqlVdbeMemTooBig(Mem * p)
 {
 	assert(p->db != 0);
-	if (p->flags & (MEM_Str | MEM_Blob)) {
+	if (mem_is_bytes(p)) {
 		int n = p->n;
 		if (p->flags & MEM_Zero) {
 			n += p->u.nZero;
@@ -2258,40 +2330,37 @@ sqlVdbeMemTooBig(Mem * p)
 int
 sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
 {
-	int f1, f2;
 	int res;
-	int combined_flags;
 
-	f1 = pMem1->flags;
-	f2 = pMem2->flags;
-	combined_flags = f1 | f2;
+	enum mem_type type1 = pMem1->type;
+	enum mem_type type2 = pMem2->type;
 
 	/* If one value is NULL, it is less than the other. If both values
 	 * are NULL, return 0.
 	 */
-	if (combined_flags & MEM_Null) {
-		return (f2 & MEM_Null) - (f1 & MEM_Null);
-	}
+	if (type1 == MEM_TYPE_NULL || type2 == MEM_TYPE_NULL)
+		return (int)(type2 == MEM_TYPE_NULL) -
+		       (int)(type1 == MEM_TYPE_NULL);
 
-	if ((combined_flags & MEM_Bool) != 0) {
-		if ((f1 & f2 & MEM_Bool) != 0) {
+	if (type1 == MEM_TYPE_BOOL || type2 == MEM_TYPE_BOOL) {
+		if (type1 == MEM_TYPE_BOOL && type2 == MEM_TYPE_BOOL) {
 			if (pMem1->u.b == pMem2->u.b)
 				return 0;
 			if (pMem1->u.b)
 				return 1;
 			return -1;
 		}
-		if ((f2 & MEM_Bool) != 0)
+		if (type2 == MEM_TYPE_BOOL)
 			return +1;
 		return -1;
 	}
 
 	/* At least one of the two values is a number
 	 */
-	if ((combined_flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0) {
-		if ((f1 & (MEM_Real | MEM_Int | MEM_UInt)) == 0)
+	if (mem_is_num(pMem1) || mem_is_num(pMem2)) {
+		if (!mem_is_num(pMem1))
 			return +1;
-		if ((f2 & (MEM_Real | MEM_Int | MEM_UInt)) == 0)
+		if (!mem_is_num(pMem2))
 			return -1;
 		mem_cmp_num(pMem1, pMem2, &res);
 		return res;
@@ -2300,11 +2369,11 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
 	/* If one value is a string and the other is a blob, the string is less.
 	 * If both are strings, compare using the collating functions.
 	 */
-	if (combined_flags & MEM_Str) {
-		if ((f1 & MEM_Str) == 0) {
+	if (type1 == MEM_TYPE_STR || type2 == MEM_TYPE_STR) {
+		if (type1 != MEM_TYPE_STR) {
 			return 1;
 		}
-		if ((f2 & MEM_Str) == 0) {
+		if (type2 != MEM_TYPE_STR) {
 			return -1;
 		}
 		mem_cmp_str(pMem1, pMem2, &res, pColl);
@@ -2322,12 +2391,13 @@ sql_vdbemem_finalize(struct Mem *mem, struct func *func)
 	assert(func != NULL);
 	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
 	assert(func->def->aggregate == FUNC_AGGREGATE_GROUP);
-	assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
+	assert(mem->type == MEM_TYPE_NULL || func == mem->u.func);
 	sql_context ctx;
 	memset(&ctx, 0, sizeof(ctx));
 	Mem t;
 	memset(&t, 0, sizeof(t));
-	t.flags = MEM_Null;
+	t.type = MEM_TYPE_NULL;
+	t.flags = 0;
 	t.db = mem->db;
 	t.field_type = field_type_MAX;
 	ctx.pOut = &t;
@@ -2356,35 +2426,35 @@ sqlVdbeCompareMsgpack(const char **key1,
 			break;
 		}
 	case MP_NIL:{
-			rc = -((pKey2->flags & MEM_Null) == 0);
+			rc = -(pKey2->type != MEM_TYPE_NULL);
 			mp_decode_nil(&aKey1);
 			break;
 		}
 	case MP_BOOL:{
 			mem1.u.b = mp_decode_bool(&aKey1);
-			if ((pKey2->flags & MEM_Bool) != 0) {
+			if (pKey2->type == MEM_TYPE_BOOL) {
 				if (mem1.u.b != pKey2->u.b)
 					rc = mem1.u.b ? 1 : -1;
 			} else {
-				rc = (pKey2->flags & MEM_Null) != 0 ? 1 : -1;
+				rc = pKey2->type == MEM_TYPE_NULL ? 1 : -1;
 			}
 			break;
 		}
 	case MP_UINT:{
 			mem1.u.u = mp_decode_uint(&aKey1);
-			if ((pKey2->flags & MEM_Int) != 0) {
+			if (pKey2->type == MEM_TYPE_INT) {
 				rc = +1;
-			} else if ((pKey2->flags & MEM_UInt) != 0) {
+			} else if (pKey2->type == MEM_TYPE_UINT) {
 				if (mem1.u.u < pKey2->u.u)
 					rc = -1;
 				else if (mem1.u.u > pKey2->u.u)
 					rc = +1;
-			} else if ((pKey2->flags & MEM_Real) != 0) {
+			} else if (pKey2->type == MEM_TYPE_DOUBLE) {
 				rc = double_compare_uint64(pKey2->u.r,
 							   mem1.u.u, -1);
-			} else if ((pKey2->flags & MEM_Null) != 0) {
+			} else if (pKey2->type == MEM_TYPE_NULL) {
 				rc = 1;
-			} else if ((pKey2->flags & MEM_Bool) != 0) {
+			} else if (pKey2->type == MEM_TYPE_BOOL) {
 				rc = 1;
 			} else {
 				rc = -1;
@@ -2393,20 +2463,20 @@ sqlVdbeCompareMsgpack(const char **key1,
 		}
 	case MP_INT:{
 			mem1.u.i = mp_decode_int(&aKey1);
-			if ((pKey2->flags & MEM_UInt) != 0) {
+			if (pKey2->type == MEM_TYPE_UINT) {
 				rc = -1;
-			} else if ((pKey2->flags & MEM_Int) != 0) {
+			} else if (pKey2->type == MEM_TYPE_INT) {
 				if (mem1.u.i < pKey2->u.i) {
 					rc = -1;
 				} else if (mem1.u.i > pKey2->u.i) {
 					rc = +1;
 				}
-			} else if (pKey2->flags & MEM_Real) {
+			} else if (pKey2->type == MEM_TYPE_DOUBLE) {
 				rc = double_compare_nint64(pKey2->u.r, mem1.u.i,
 							   -1);
-			} else if ((pKey2->flags & MEM_Null) != 0) {
+			} else if (pKey2->type == MEM_TYPE_NULL) {
 				rc = 1;
-			} else if ((pKey2->flags & MEM_Bool) != 0) {
+			} else if (pKey2->type == MEM_TYPE_BOOL) {
 				rc = 1;
 			} else {
 				rc = -1;
@@ -2420,21 +2490,21 @@ sqlVdbeCompareMsgpack(const char **key1,
 	case MP_DOUBLE:{
 			mem1.u.r = mp_decode_double(&aKey1);
  do_float:
-			if ((pKey2->flags & MEM_Int) != 0) {
+			if (pKey2->type == MEM_TYPE_INT) {
 				rc = double_compare_nint64(mem1.u.r, pKey2->u.i,
 							   1);
-			} else if (pKey2->flags & MEM_UInt) {
+			} else if (pKey2->type == MEM_TYPE_UINT) {
 				rc = double_compare_uint64(mem1.u.r,
 							   pKey2->u.u, 1);
-			} else if (pKey2->flags & MEM_Real) {
+			} else if (pKey2->type == MEM_TYPE_DOUBLE) {
 				if (mem1.u.r < pKey2->u.r) {
 					rc = -1;
 				} else if (mem1.u.r > pKey2->u.r) {
 					rc = +1;
 				}
-			} else if ((pKey2->flags & MEM_Null) != 0) {
+			} else if (pKey2->type == MEM_TYPE_NULL) {
 				rc = 1;
-			} else if ((pKey2->flags & MEM_Bool) != 0) {
+			} else if (pKey2->type == MEM_TYPE_BOOL) {
 				rc = 1;
 			} else {
 				rc = -1;
@@ -2442,7 +2512,7 @@ sqlVdbeCompareMsgpack(const char **key1,
 			break;
 		}
 	case MP_STR:{
-			if (pKey2->flags & MEM_Str) {
+			if (pKey2->type == MEM_TYPE_STR) {
 				struct key_def *key_def = unpacked->key_def;
 				mem1.n = mp_decode_strl(&aKey1);
 				mem1.z = (char *)aKey1;
@@ -2450,14 +2520,15 @@ sqlVdbeCompareMsgpack(const char **key1,
 				struct coll *coll =
 					key_def->parts[key2_idx].coll;
 				if (coll != NULL) {
-					mem1.flags = MEM_Str;
+					mem1.type = MEM_TYPE_STR;
+					mem1.flags = 0;
 					rc = vdbeCompareMemString(&mem1, pKey2,
 								  coll);
 				} else {
 					goto do_bin_cmp;
 				}
 			} else {
-				rc = (pKey2->flags & MEM_Blob) ? -1 : +1;
+				rc = pKey2->type == MEM_TYPE_BIN ? -1 : +1;
 			}
 			break;
 		}
@@ -2466,7 +2537,7 @@ sqlVdbeCompareMsgpack(const char **key1,
 			mem1.z = (char *)aKey1;
 			aKey1 += mem1.n;
  do_blob:
-			if (pKey2->flags & MEM_Blob) {
+			if (pKey2->type == MEM_TYPE_BIN) {
 				if (pKey2->flags & MEM_Zero) {
 					if (!isAllZero
 					    ((const char *)mem1.z, mem1.n)) {
@@ -2533,8 +2604,8 @@ mem_from_mp_ephemeral(struct Mem *mem, const char *buf, uint32_t *len)
 		mem->z = (char *)buf;
 		mp_next(&buf);
 		mem->n = buf - mem->z;
-		mem->flags = MEM_Blob | MEM_Ephem | MEM_Subtype;
-		mem->subtype = SQL_SUBTYPE_MSGPACK;
+		mem->type = MEM_TYPE_ARRAY;
+		mem->flags = MEM_Ephem;
 		mem->field_type = FIELD_TYPE_ARRAY;
 		break;
 	}
@@ -2542,8 +2613,8 @@ mem_from_mp_ephemeral(struct Mem *mem, const char *buf, uint32_t *len)
 		mem->z = (char *)buf;
 		mp_next(&buf);
 		mem->n = buf - mem->z;
-		mem->flags = MEM_Blob | MEM_Ephem | MEM_Subtype;
-		mem->subtype = SQL_SUBTYPE_MSGPACK;
+		mem->type = MEM_TYPE_MAP;
+		mem->flags = MEM_Ephem;
 		mem->field_type = FIELD_TYPE_MAP;
 		break;
 	}
@@ -2551,39 +2622,45 @@ mem_from_mp_ephemeral(struct Mem *mem, const char *buf, uint32_t *len)
 		mem->z = (char *)buf;
 		mp_next(&buf);
 		mem->n = buf - mem->z;
-		mem->flags = MEM_Blob | MEM_Ephem;
+		mem->type = MEM_TYPE_BIN;
+		mem->flags = MEM_Ephem;
 		mem->field_type = FIELD_TYPE_VARBINARY;
 		break;
 	}
 	case MP_NIL: {
 		mp_decode_nil(&buf);
-		mem->flags = MEM_Null;
+		mem->type = MEM_TYPE_NULL;
+		mem->flags = 0;
 		mem->field_type = field_type_MAX;
 		break;
 	}
 	case MP_BOOL: {
 		mem->u.b = mp_decode_bool(&buf);
-		mem->flags = MEM_Bool;
+		mem->type = MEM_TYPE_BOOL;
+		mem->flags = 0;
 		mem->field_type = FIELD_TYPE_BOOLEAN;
 		break;
 	}
 	case MP_UINT: {
 		uint64_t v = mp_decode_uint(&buf);
 		mem->u.u = v;
-		mem->flags = MEM_UInt;
+		mem->type = MEM_TYPE_UINT;
+		mem->flags = 0;
 		mem->field_type = FIELD_TYPE_INTEGER;
 		break;
 	}
 	case MP_INT: {
 		mem->u.i = mp_decode_int(&buf);
-		mem->flags = MEM_Int;
+		mem->type = MEM_TYPE_INT;
+		mem->flags = 0;
 		mem->field_type = FIELD_TYPE_INTEGER;
 		break;
 	}
 	case MP_STR: {
 		/* XXX u32->int */
 		mem->n = (int) mp_decode_strl(&buf);
-		mem->flags = MEM_Str | MEM_Ephem;
+		mem->type = MEM_TYPE_STR;
+		mem->flags = MEM_Ephem;
 		mem->field_type = FIELD_TYPE_STRING;
 install_blob:
 		mem->z = (char *)buf;
@@ -2593,17 +2670,20 @@ install_blob:
 	case MP_BIN: {
 		/* XXX u32->int */
 		mem->n = (int) mp_decode_binl(&buf);
-		mem->flags = MEM_Blob | MEM_Ephem;
+		mem->type = MEM_TYPE_BIN;
+		mem->flags = MEM_Ephem;
 		mem->field_type = FIELD_TYPE_VARBINARY;
 		goto install_blob;
 	}
 	case MP_FLOAT: {
 		mem->u.r = mp_decode_float(&buf);
 		if (sqlIsNaN(mem->u.r)) {
-			mem->flags = MEM_Null;
+			mem->type = MEM_TYPE_NULL;
+			mem->flags = 0;
 			mem->field_type = FIELD_TYPE_DOUBLE;
 		} else {
-			mem->flags = MEM_Real;
+			mem->type = MEM_TYPE_DOUBLE;
+			mem->flags = 0;
 			mem->field_type = FIELD_TYPE_DOUBLE;
 		}
 		break;
@@ -2611,10 +2691,12 @@ install_blob:
 	case MP_DOUBLE: {
 		mem->u.r = mp_decode_double(&buf);
 		if (sqlIsNaN(mem->u.r)) {
-			mem->flags = MEM_Null;
+			mem->type = MEM_TYPE_NULL;
+			mem->flags = 0;
 			mem->field_type = FIELD_TYPE_DOUBLE;
 		} else {
-			mem->flags = MEM_Real;
+			mem->type = MEM_TYPE_DOUBLE;
+			mem->flags = 0;
 			mem->field_type = FIELD_TYPE_DOUBLE;
 		}
 		break;
@@ -2631,7 +2713,7 @@ mem_from_mp(struct Mem *mem, const char *buf, uint32_t *len)
 {
 	if (mem_from_mp_ephemeral(mem, buf, len) != 0)
 		return -1;
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
+	if (mem_is_bytes(mem)) {
 		assert((mem->flags & MEM_Ephem) != 0);
 		if (sqlVdbeMemGrow(mem, mem->n, 1) != 0)
 			return -1;
@@ -2643,35 +2725,41 @@ void
 mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var)
 {
 	assert(memIsValid(var));
-	int64_t i;
-	if (var->flags & MEM_Null) {
+	switch (var->type) {
+	case MEM_TYPE_NULL:
 		mpstream_encode_nil(stream);
-	} else if (var->flags & MEM_Real) {
-		mpstream_encode_double(stream, var->u.r);
-	} else if (var->flags & MEM_Int) {
-		i = var->u.i;
-		mpstream_encode_int(stream, i);
-	} else if (var->flags & MEM_UInt) {
-		i = var->u.u;
-		mpstream_encode_uint(stream, i);
-	} else if (var->flags & MEM_Str) {
+		return;
+	case MEM_TYPE_STR:
 		mpstream_encode_strn(stream, var->z, var->n);
-	} else if (var->flags & MEM_Bool) {
-		mpstream_encode_bool(stream, var->u.b);
-	} else {
-		/*
-		 * Emit BIN header iff the BLOB doesn't store
-		 * MsgPack content.
-		 */
-		if (!mem_has_msgpack_subtype(var)) {
-			uint32_t binl = var->n +
-					((var->flags & MEM_Zero) ?
-					var->u.nZero : 0);
-			mpstream_encode_binl(stream, binl);
+		return;
+	case MEM_TYPE_INT:
+		mpstream_encode_int(stream, var->u.i);
+		return;
+	case MEM_TYPE_UINT:
+		mpstream_encode_uint(stream, var->u.u);
+		return;
+	case MEM_TYPE_DOUBLE:
+		mpstream_encode_double(stream, var->u.r);
+		return;
+	case MEM_TYPE_BIN:
+		if ((var->flags & MEM_Zero) != 0) {
+			mpstream_encode_binl(stream, var->n + var->u.nZero);
+			mpstream_memcpy(stream, var->z, var->n);
+			mpstream_memset(stream, 0, var->u.nZero);
+		} else {
+			mpstream_encode_binl(stream, var->n);
+			mpstream_memcpy(stream, var->z, var->n);
 		}
+		return;
+	case MEM_TYPE_ARRAY:
+	case MEM_TYPE_MAP:
 		mpstream_memcpy(stream, var->z, var->n);
-		if (var->flags & MEM_Zero)
-			mpstream_memset(stream, 0, var->u.nZero);
+		return;
+	case MEM_TYPE_BOOL:
+		mpstream_encode_bool(stream, var->u.b);
+		return;
+	default:
+		unreachable();
 	}
 }
 
@@ -2734,24 +2822,26 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
 	assert(is_flat == true);
 	for (uint32_t i = 0; i < port->mem_count; i++) {
 		struct Mem *mem = (struct Mem *)port->mem + i;
-		switch (mem->flags & MEM_PURE_TYPE_MASK) {
-		case MEM_Int:
+		switch (mem->type) {
+		case MEM_TYPE_INT:
 			luaL_pushint64(L, mem->u.i);
 			break;
-		case MEM_UInt:
+		case MEM_TYPE_UINT:
 			luaL_pushuint64(L, mem->u.u);
 			break;
-		case MEM_Real:
+		case MEM_TYPE_DOUBLE:
 			lua_pushnumber(L, mem->u.r);
 			break;
-		case MEM_Str:
-		case MEM_Blob:
+		case MEM_TYPE_STR:
+		case MEM_TYPE_BIN:
+		case MEM_TYPE_MAP:
+		case MEM_TYPE_ARRAY:
 			lua_pushlstring(L, mem->z, mem->n);
 			break;
-		case MEM_Null:
+		case MEM_TYPE_NULL:
 			lua_pushnil(L);
 			break;
-		case MEM_Bool:
+		case MEM_TYPE_BOOL:
 			lua_pushboolean(L, mem->u.b);
 			break;
 		default:
@@ -2844,23 +2934,28 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size)
 		mem_clear(&val[i]);
 		switch (field.type) {
 		case MP_BOOL:
-			val[i].flags = MEM_Bool;
+			val[i].type = MEM_TYPE_BOOL;
+			assert(val[i].flags == 0);
 			val[i].u.b = field.bval;
 			break;
 		case MP_FLOAT:
-			val[i].flags = MEM_Real;
+			val[i].type = MEM_TYPE_DOUBLE;
+			assert(val[i].flags == 0);
 			val[i].u.r = field.fval;
 			break;
 		case MP_DOUBLE:
-			val[i].flags = MEM_Real;
+			val[i].type = MEM_TYPE_DOUBLE;
+			assert(val[i].flags == 0);
 			val[i].u.r = field.dval;
 			break;
 		case MP_INT:
-			val[i].flags = MEM_Int;
+			val[i].type = MEM_TYPE_INT;
+			assert(val[i].flags == 0);
 			val[i].u.i = field.ival;
 			break;
 		case MP_UINT:
-			val[i].flags = MEM_UInt;
+			val[i].type = MEM_TYPE_UINT;
+			assert(val[i].flags == 0);
 			val[i].u.i = field.ival;
 			break;
 		case MP_STR:
@@ -2919,23 +3014,28 @@ port_c_get_vdbemem(struct port *base, uint32_t *size)
 		const char *str;
 		switch (mp_typeof(*data)) {
 		case MP_BOOL:
-			val[i].flags = MEM_Bool;
+			val[i].type = MEM_TYPE_BOOL;
+			assert(val[i].flags == 0);
 			val[i].u.b = mp_decode_bool(&data);
 			break;
 		case MP_FLOAT:
-			val[i].flags = MEM_Real;
+			val[i].type = MEM_TYPE_DOUBLE;
+			assert(val[i].flags == 0);
 			val[i].u.r = mp_decode_float(&data);
 			break;
 		case MP_DOUBLE:
-			val[i].flags = MEM_Real;
+			val[i].type = MEM_TYPE_DOUBLE;
+			assert(val[i].flags == 0);
 			val[i].u.r = mp_decode_double(&data);
 			break;
 		case MP_INT:
-			val[i].flags = MEM_Int;
+			val[i].type = MEM_TYPE_INT;
+			assert(val[i].flags == 0);
 			val[i].u.i = mp_decode_int(&data);
 			break;
 		case MP_UINT:
-			val[i].flags = MEM_UInt;
+			val[i].type = MEM_TYPE_UINT;
+			assert(val[i].flags == 0);
 			val[i].u.u = mp_decode_uint(&data);
 			break;
 		case MP_STR:
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 6fc15617d..a4a0d2223 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -37,73 +37,78 @@ struct region;
 struct mpstream;
 struct VdbeFrame;
 
-/*
- * Internally, the vdbe manipulates nearly all SQL values as Mem
- * structures. Each Mem struct may cache multiple representations (string,
- * integer etc.) of the same value.
- */
+enum mem_type {
+	MEM_TYPE_NULL = 1u << MP_NIL,
+	MEM_TYPE_UINT = 1u << MP_UINT,
+	MEM_TYPE_INT = 1u << MP_INT,
+	MEM_TYPE_STR = 1u << MP_STR,
+	MEM_TYPE_BIN = 1u << MP_BIN,
+	MEM_TYPE_ARRAY = 1u << MP_ARRAY,
+	MEM_TYPE_MAP = 1u << MP_MAP,
+	MEM_TYPE_BOOL = 1u << MP_BOOL,
+	MEM_TYPE_FLOAT = 1u << MP_FLOAT,
+	MEM_TYPE_DOUBLE = 1u << MP_DOUBLE,
+	MEM_TYPE_INVALID = 1u << MP_EXT,
+	MEM_TYPE_FRAME = 1u << (MP_EXT + 1),
+	MEM_TYPE_PTR = 1u << (MP_EXT + 2),
+	MEM_TYPE_AGG = 1u << (MP_EXT + 3),
+};
+
+/** Internally, the vdbe manipulates nearly all SQL values as Mem structures. */
 struct Mem {
 	union MemValue {
-		double r;	/* Real value used when MEM_Real is set in flags */
-		i64 i;		/* Integer value used when MEM_Int is set in flags */
-		uint64_t u;	/* Unsigned integer used when MEM_UInt is set. */
-		bool b;         /* Boolean value used when MEM_Bool is set in flags */
-		int nZero;	/* Used when bit MEM_Zero is set in flags */
-		void *p;	/* Generic pointer */
+		/** Double value when MEM type is MEM_TYPE_DOUBLE. */
+		double r;
+		/** Negative integer value when MEM type is MEM_TYPE_INT. */
+		i64 i;
+		/** Unsigned integer value when MEM type is MEM_TYPE_UINT. */
+		uint64_t u;
+		/** Boolean value when MEM type is MEM_TYPE_BOOL. */
+		bool b;
 		/**
-		 * A pointer to function implementation.
-		 * Used only when flags==MEM_Agg.
+		 * Number of zeroes when MEM type is MEM_TYPE_BIN and MEM_Zero
+		 * flag is set.
 		 */
+		int nZero;
+		/** Generic pointer when MEM type is MEM_TYPE_PTR. */
+		void *p;
+		/** Pointer to function when MEM type is MEM_TYPE_AGG. */
 		struct func *func;
-		struct VdbeFrame *pFrame;	/* Used when flags==MEM_Frame */
+		/** Pointer to frame when MEM type is MEM_TYPE_FRAME. */
+		struct VdbeFrame *pFrame;
 	} u;
-	u32 flags;		/* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
-	/** Subtype for this value. */
-	enum sql_subtype subtype;
+	/** Type of the value this MEM contains. */
+	enum mem_type type;
+	/** Additional information for MEM types. */
+	u32 flags;
 	/**
-	 * If value is fetched from tuple, then this property
-	 * contains type of corresponding space's field. If it's
-	 * value field_type_MAX then we can rely on on format
-	 * (msgpack) type which is represented by 'flags'.
+	 * Field type of the value this MEM contains. Should be field_type_MAX
+	 * or field type compatible with MEM type.
 	 */
 	enum field_type field_type;
-	int n;			/* size (in bytes) of string value, excluding trailing '\0' */
-	char *z;		/* String or BLOB value */
-	/* ShallowCopy only needs to copy the information above */
-	char *zMalloc;		/* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */
-	int szMalloc;		/* Size of the zMalloc allocation */
-	u32 uTemp;		/* Transient storage for serial_type in OP_MakeRecord */
-	sql *db;		/* The associated database connection */
-	void (*xDel) (void *);	/* Destructor for Mem.z - only valid if MEM_Dyn */
+	/**
+	 * The size in bytes for a STRING, VARBINARY, MAP, or ARRAY value. For a
+	 * STRING, if MEM_Term is set, the actual size is n + 1, since there is
+	 * trailing '\0'.
+	 */
+	int n;
+	/** STRING, VARBINARY, MAP, or ARRAY if set to the appropriate type. */
+	char *z;
+	/** Memory, allocated by MEM. */
+	char *zMalloc;
+	/** Size of memory, allocated by MEM. */
+	int szMalloc;
+	/** Database that contains this MEM. */
+	struct sql *db;
+	/** Destructor in case MEM_Dyn is set. */
+	void (*xDel) (void *);
 #ifdef SQL_DEBUG
-	Mem *pScopyFrom;	/* This Mem is a shallow copy of pScopyFrom */
-	void *pFiller;		/* So that sizeof(Mem) is a multiple of 8 */
+	/** This Mem is a shallow copy of pScopyFrom. */
+	struct Mem *pScopyFrom;
 #endif
 };
 
-/* One or more of the following flags are set to indicate the validOK
- * representations of the value stored in the Mem struct.
- *
- * If the MEM_Null flag is set, then the value is an SQL NULL value.
- * No other flags may be set in this case.
- *
- * If the MEM_Str flag is set then Mem.z points at a string representation.
- * Usually this is encoded in the same unicode encoding as the main
- * database (see below for exceptions). If the MEM_Term flag is also
- * set, then the string is nul terminated. The MEM_Int and MEM_Real
- * flags may coexist with the MEM_Str flag.
- */
-#define MEM_Null      0x0001	/* Value is NULL */
-#define MEM_Str       0x0002	/* Value is a string */
-#define MEM_Int       0x0004	/* Value is an integer */
-#define MEM_Real      0x0008	/* Value is a real number */
-#define MEM_Blob      0x0010	/* Value is a BLOB */
-#define MEM_Bool      0x0020    /* Value is a bool */
-#define MEM_UInt      0x0040	/* Value is an unsigned integer */
-#define MEM_Frame     0x0080	/* Value is a VdbeFrame object */
-#define MEM_Undefined 0x0100	/* Value is undefined */
 #define MEM_Cleared   0x0200	/* NULL set by OP_Null, not from data */
-#define MEM_TypeMask  0x83ff	/* Mask of type bits */
 
 /* Whenever Mem contains a valid string or blob representation, one of
  * the following flags must be set to determine the memory management
@@ -114,181 +119,158 @@ struct Mem {
 #define MEM_Dyn       0x0800	/* Need to call Mem.xDel() on Mem.z */
 #define MEM_Static    0x1000	/* Mem.z points to a static string */
 #define MEM_Ephem     0x2000	/* Mem.z points to an ephemeral string */
-#define MEM_Agg       0x4000	/* Mem.z points to an agg function context */
 #define MEM_Zero      0x8000	/* Mem.i contains count of 0s appended to blob */
-#define MEM_Subtype   0x10000	/* Mem.eSubtype is valid */
-#define MEM_Ptr       0x20000	/* Value is a generic pointer */
-
-/**
- * In contrast to Mem_TypeMask, this one allows to get
- * pure type of memory cell, i.e. without _Dyn/_Zero and other
- * auxiliary flags.
- */
-enum {
-	MEM_PURE_TYPE_MASK = 0x7f
-};
-
-static_assert(MEM_PURE_TYPE_MASK == (MEM_Null | MEM_Str | MEM_Int | MEM_Real |
-				     MEM_Blob | MEM_Bool | MEM_UInt),
-	      "value of type mask must consist of corresponding to memory "\
-	      "type bits");
 
 static inline bool
 mem_is_null(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Null) != 0;
+	return mem->type == MEM_TYPE_NULL;
 }
 
 static inline bool
 mem_is_uint(const struct Mem *mem)
 {
-	return (mem->flags & MEM_UInt) != 0;
+	return mem->type == MEM_TYPE_UINT;
 }
 
 static inline bool
 mem_is_nint(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Int) != 0;
+	return mem->type == MEM_TYPE_INT;
 }
 
 static inline bool
 mem_is_str(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Str) != 0;
+	return mem->type == MEM_TYPE_STR;
 }
 
 static inline bool
 mem_is_num(const struct Mem *mem)
 {
-	return (mem->flags & (MEM_Real | MEM_Int | MEM_UInt)) != 0;
+	enum mem_type type = mem->type;
+	return (type & (MEM_TYPE_UINT | MEM_TYPE_INT | MEM_TYPE_DOUBLE)) != 0;
 }
 
 static inline bool
 mem_is_double(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Real) != 0;
+	return mem->type == MEM_TYPE_DOUBLE;
 }
 
 static inline bool
 mem_is_int(const struct Mem *mem)
 {
-	return (mem->flags & (MEM_Int | MEM_UInt)) != 0;
+	return (mem->type & (MEM_TYPE_UINT | MEM_TYPE_INT)) != 0;
 }
 
 static inline bool
 mem_is_bool(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Bool) != 0;
+	return mem->type == MEM_TYPE_BOOL;
 }
 
 static inline bool
 mem_is_bin(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Blob) != 0 && (mem->flags & MEM_Subtype) == 0;
+	return mem->type == MEM_TYPE_BIN;
 }
 
 static inline bool
 mem_is_map(const struct Mem *mem)
 {
-	assert((mem->flags & MEM_Subtype) == 0 || (mem->flags & MEM_Blob) != 0);
-	assert((mem->flags & MEM_Subtype) == 0 ||
-	       mem->subtype == SQL_SUBTYPE_MSGPACK);
-	return (mem->flags & MEM_Subtype) != 0 && mp_typeof(*mem->z) == MP_MAP;
+	return mem->type == MEM_TYPE_MAP;
 }
 
 static inline bool
 mem_is_array(const struct Mem *mem)
 {
-	assert((mem->flags & MEM_Subtype) == 0 || (mem->flags & MEM_Blob) != 0);
-	assert((mem->flags & MEM_Subtype) == 0 ||
-	       mem->subtype == SQL_SUBTYPE_MSGPACK);
-	return (mem->flags & MEM_Subtype) != 0 &&
-	       mp_typeof(*mem->z) == MP_ARRAY;
+	return mem->type == MEM_TYPE_ARRAY;
 }
 
 static inline bool
 mem_is_bin_ext(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Blob) != 0;
+	enum mem_type type = mem->type;
+	return (type & (MEM_TYPE_BIN | MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0;
 }
 
 static inline bool
 mem_is_agg(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Agg) != 0;
+	return mem->type == MEM_TYPE_AGG;
 }
 
 static inline bool
 mem_is_bytes(const struct Mem *mem)
 {
-	return (mem->flags & (MEM_Blob | MEM_Str)) != 0;
+	return (mem->type & (MEM_TYPE_BIN | MEM_TYPE_STR |
+			     MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0;
 }
 
 static inline bool
 mem_is_frame(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Frame) != 0;
+	return mem->type == MEM_TYPE_FRAME;
 }
 
 static inline bool
 mem_is_invalid(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Undefined) != 0;
+	return mem->type == MEM_TYPE_INVALID;
 }
 
 static inline bool
 mem_is_static(const struct Mem *mem)
 {
-	assert((mem->flags & (MEM_Str | MEM_Blob)) != 0);
+	assert(mem_is_bytes(mem));
 	return (mem->flags & MEM_Static) != 0;
 }
 
 static inline bool
 mem_is_ephemeral(const struct Mem *mem)
 {
-	assert((mem->flags & (MEM_Str | MEM_Blob)) != 0);
+	assert(mem_is_bytes(mem));
 	return (mem->flags & MEM_Ephem) != 0;
 }
 
 static inline bool
 mem_is_dynamic(const struct Mem *mem)
 {
-	assert((mem->flags & (MEM_Str | MEM_Blob)) != 0);
+	assert(mem_is_bytes(mem));
 	return (mem->flags & MEM_Dyn) != 0;
 }
 
 static inline bool
 mem_is_allocated(const struct Mem *mem)
 {
-	return (mem->flags & (MEM_Str | MEM_Blob)) != 0 &&
-	       mem->z == mem->zMalloc;
+	return mem_is_bytes(mem) && mem->z == mem->zMalloc;
 }
 
 static inline bool
 mem_is_cleared(const struct Mem *mem)
 {
-	assert((mem->flags & MEM_Cleared) == 0 || (mem->flags & MEM_Null) != 0);
+	assert((mem->flags & MEM_Cleared) == 0 || mem->type == MEM_TYPE_NULL);
 	return (mem->flags & MEM_Cleared) != 0;
 }
 
 static inline bool
 mem_is_zerobin(const struct Mem *mem)
 {
-	assert((mem->flags & MEM_Zero) == 0 || (mem->flags & MEM_Blob) != 0);
+	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
 	return (mem->flags & MEM_Zero) != 0;
 }
 
 static inline bool
 mem_is_same_type(const struct Mem *mem1, const struct Mem *mem2)
 {
-	return (mem1->flags & MEM_PURE_TYPE_MASK) ==
-	       (mem2->flags & MEM_PURE_TYPE_MASK);
+	return mem1->type == mem2->type;
 }
 
 static inline bool
 mem_is_any_null(const struct Mem *mem1, const struct Mem *mem2)
 {
-	return ((mem1->flags | mem2->flags) & MEM_Null) != 0;
+	return mem1->type == MEM_TYPE_NULL || mem2->type == MEM_TYPE_NULL;
 }
 
 /**
@@ -949,7 +931,7 @@ registerTrace(int iReg, Mem *p);
  * Return true if a memory cell is not marked as invalid.  This macro
  * is for use inside assert() statements only.
  */
-#define memIsValid(M)  ((M)->flags & MEM_Undefined)==0
+#define memIsValid(M)  ((M)->type != MEM_TYPE_INVALID)
 #endif
 
 int sqlVdbeMemExpandBlob(struct Mem *);
@@ -975,8 +957,9 @@ int sqlVdbeMemTooBig(Mem *);
 /* Return TRUE if Mem X contains dynamically allocated content - anything
  * that needs to be deallocated to avoid a leak.
  */
-#define VdbeMemDynamic(X)  \
-  (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_Frame))!=0)
+#define VdbeMemDynamic(X) ((X)->type == MEM_TYPE_AGG || \
+			   (X)->type == MEM_TYPE_FRAME || \
+			   ((X)->flags & MEM_Dyn) != 0)
 
 
 int sqlMemCompare(const Mem *, const Mem *, const struct coll *);
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index ba5c08a00..1aa201466 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -93,7 +93,8 @@ valueNew(sql * db, struct ValueNewStat4Ctx *p)
 			pRec->aMem = (Mem *)((char *) pRec +
 					     ROUND8(sizeof(UnpackedRecord)));
 			for (uint32_t i = 0; i < part_count; i++) {
-				pRec->aMem[i].flags = MEM_Null;
+				pRec->aMem[i].type = MEM_NULL;
+				pRec->aMem[i].flags = 0;
 				pRec->aMem[i].db = db;
 			}
 			p->ppRec[0] = pRec;

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

* Re: [Tarantool-patches] [PATCH v2 3/3] sql: replace MEM-type flags by enum mem_type
  2021-04-27 16:55 ` [Tarantool-patches] [PATCH v2 3/3] sql: replace " Mergen Imeev via Tarantool-patches
@ 2021-04-29 21:09   ` Vladislav Shpilevoy via Tarantool-patches
  2021-05-17 12:18     ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 11+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-04-29 21:09 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

I appreciate the work you did here!

>>>  	mem->field_type = FIELD_TYPE_VARBINARY;
>>>  	return 0;
>>>  }> @@ -1168,9 +1229,9 @@ mem_get_bin(const struct Mem *mem, const char **s)
>>>  int
>>>  mem_len(const struct Mem *mem, uint32_t *len)
>>>  {
>>> -	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
>>> +	if (mem->type != MEM_STR && mem->type != MEM_BIN)
>>>  		return -1;
>>
>> 8. Is it -1 for MAP and ARRAY intentionally? Previously they
>> were included into MEM_Blob.
>>
>> I see mem_concat() didn't check for subtypes. Does it mean we
>> could concat two MP_ARRAYs into something invalid? If we could,
>> your patch probably just fixed it. Could you check and add a
>> test if so?
>>
> Fixed. You were right, it is actually possible to concatenate two maps, map and
> array, map and varbinary and so on. I returned ability to do this. Should I add
> a test?

That behaviour is not correct, and should raise an error I think. You
can add a test, but if it works now, then it should have a comment
saying that this test is bad and must start failing in the future. And
now you might test it just to ensure it does not crash anywhere.

>>> @@ -114,175 +120,153 @@ struct Mem {
>>
>> <...>
>>
>>>  static inline bool
>>>  mem_is_agg(const struct Mem *mem)
>>>  {
>>> -	return (mem->flags & MEM_Agg) != 0;
>>> +	return mem->type == MEM_AGG;
>>>  }
>>>  
>>>  static inline bool
>>>  mem_is_bytes(const struct Mem *mem)
>>>  {
>>> -	return (mem->flags & (MEM_Blob | MEM_Str)) != 0;
>>> +	enum mem_type type = mem->type;
>>> +	return type == MEM_BIN || type == MEM_MAP || type == MEM_ARRAY ||
>>> +	       type == MEM_STR;
>>>  }
>> 15. It was good when we could check several rare cases at one
>> branch. Maybe you could preserve the bitwise structure of the
>> types? Then we could keep the bit operations and save a few
>> branches. You didn't think of it, or do we go for normal numbers
>> intentionally?
>>
> I did it, although I'm not sure if this is the right choice: although it makes
> our code more compact in some cases, it makes it a little less readable.

I just realized another issue which you will get with UUID and DECIMAL. They
are not present in mp_type, so there won't be a direct mapping anyway. They
are encoded as MP_EXT + MP_UUID/MP_DECIMAL. It fits into a 64bit integer,
but it is a binary header, and is not in mp_type value range. The same for
MEM_TYPE_PTR, MEM_TYPE_FRAME, and other SQL-specific.

See 5 comments below.

> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index b6ff6397f..4f189cac4 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -89,39 +89,36 @@ mem_str(const struct Mem *mem)

<...>

>  static inline void
>  mem_clear(struct Mem *mem)
>  {
> -	if ((mem->flags & (MEM_Agg | MEM_Dyn | MEM_Frame)) != 0) {
> -		if ((mem->flags & MEM_Agg) != 0)
> -			sql_vdbemem_finalize(mem, mem->u.func);
> -		assert((mem->flags & MEM_Agg) == 0);
> -		if ((mem->flags & MEM_Dyn) != 0) {
> -			assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
> -			mem->xDel((void *)mem->z);
> -		} else if ((mem->flags & MEM_Frame) != 0) {
> -			struct VdbeFrame *frame = mem->u.pFrame;
> -			frame->pParent = frame->v->pDelFrame;
> -			frame->v->pDelFrame = frame;
> -		}
> -	}
> -	mem->flags = MEM_Null;
> +	if (mem->type == MEM_TYPE_AGG) {
> +		sql_vdbemem_finalize(mem, mem->u.func);
> +		assert(mem->type != MEM_TYPE_AGG);
> +	} else if (mem->type == MEM_TYPE_FRAME) {
> +		struct VdbeFrame *frame = mem->u.pFrame;
> +		frame->pParent = frame->v->pDelFrame;
> +		frame->v->pDelFrame = frame;
> +	} else if ((mem->flags & MEM_Dyn) != 0) {
> +		assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
> +		mem->xDel((void *)mem->z);
> +	}

1. With the new bitwise structure you could reduce the diff.
For example, here there was one 'if' for a non-common case of
something allocated dynamically. Now there are 3 'if's.

> +	mem->type = MEM_TYPE_NULL;
> +	mem->flags = 0;
>  	mem->field_type = field_type_MAX;
>  }
> @@ -268,11 +271,13 @@ mem_set_str0_allocated(struct Mem *mem, char *value)
>  int
>  mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
>  {
> -	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0 && mem->z == value) {
> +	if ((mem->type == MEM_TYPE_STR ||
> +	     mem->type == MEM_TYPE_BIN) && mem->z == value) {

2. The only reason I proposed to keep the types in their
own bits was to reduce number of the branches in the 'if's.
So as you could write flags & (MEM_TYPE_STR | MEM_TYPE_BIN) != 0
for checking for multiple types instead of using || which adds
an implicit branch.

The same in mem_copy_bin(), mem_to_int(), mem_to_int_precise(),
mem_to_double(), mem_to_number(), end of mem_cast_explicit()
(the FIELD_TYPE_SCALAR case), in a few places in mem_cast_implicit(),
mem_cast_implicit_old(), mem_get_int(), mem_get_uint(), try_return_null(),
mem_concat(), get_number(), mem_cmp_bin(), sqlVdbeCheckMemInvariants(),
end of memTracePrint(). Maybe more which I missed.

>  		/* Own value, but might be ephemeral. Make it own if so. */
>  		if (sqlVdbeMemGrow(mem, len, 1) != 0)
>  			return -1;
> -		mem->flags = MEM_Str;
> +		mem->type = MEM_TYPE_STR;
> +		mem->flags = 0;
>  		mem->field_type = FIELD_TYPE_STRING;
>  		return 0;
>  	}
> @@ -2258,40 +2330,37 @@ sqlVdbeMemTooBig(Mem * p)
>  int
>  sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
>  {
> -	int f1, f2;
>  	int res;
> -	int combined_flags;
>  
> -	f1 = pMem1->flags;
> -	f2 = pMem2->flags;
> -	combined_flags = f1 | f2;
> +	enum mem_type type1 = pMem1->type;
> +	enum mem_type type2 = pMem2->type;
>  
>  	/* If one value is NULL, it is less than the other. If both values
>  	 * are NULL, return 0.
>  	 */
> -	if (combined_flags & MEM_Null) {
> -		return (f2 & MEM_Null) - (f1 & MEM_Null);
> -	}
> +	if (type1 == MEM_TYPE_NULL || type2 == MEM_TYPE_NULL)

3. With the bitwise types you could have combined_types and
keep only one branch in such places.

> +		return (int)(type2 == MEM_TYPE_NULL) -
> +		       (int)(type1 == MEM_TYPE_NULL);
>  
> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index 6fc15617d..a4a0d2223 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -37,73 +37,78 @@ struct region;
>  struct mpstream;
>  struct VdbeFrame;
>  
> -/*
> - * Internally, the vdbe manipulates nearly all SQL values as Mem
> - * structures. Each Mem struct may cache multiple representations (string,
> - * integer etc.) of the same value.
> - */
> +enum mem_type {
> +	MEM_TYPE_NULL = 1u << MP_NIL,
> +	MEM_TYPE_UINT = 1u << MP_UINT,
> +	MEM_TYPE_INT = 1u << MP_INT,
> +	MEM_TYPE_STR = 1u << MP_STR,
> +	MEM_TYPE_BIN = 1u << MP_BIN,
> +	MEM_TYPE_ARRAY = 1u << MP_ARRAY,
> +	MEM_TYPE_MAP = 1u << MP_MAP,
> +	MEM_TYPE_BOOL = 1u << MP_BOOL,
> +	MEM_TYPE_FLOAT = 1u << MP_FLOAT,
> +	MEM_TYPE_DOUBLE = 1u << MP_DOUBLE,
> +	MEM_TYPE_INVALID = 1u << MP_EXT,
> +	MEM_TYPE_FRAME = 1u << (MP_EXT + 1),
> +	MEM_TYPE_PTR = 1u << (MP_EXT + 2),
> +	MEM_TYPE_AGG = 1u << (MP_EXT + 3),

4. Why do you stick to mp_types? Why can't have your own
bits? Anyway I don't see now any easy conversions between
mem_type and mp_type. It is also kind of incorrect because
MEM_TYPE_PTR = MP_EXT + 2 is not a pointer in MessagePack,
and the same for the other extensions.

> +};
> +
> +/** Internally, the vdbe manipulates nearly all SQL values as Mem structures. */
>  struct Mem {
>  	union MemValue {
> -		double r;	/* Real value used when MEM_Real is set in flags */
> -		i64 i;		/* Integer value used when MEM_Int is set in flags */
> -		uint64_t u;	/* Unsigned integer used when MEM_UInt is set. */
> -		bool b;         /* Boolean value used when MEM_Bool is set in flags */
> -		int nZero;	/* Used when bit MEM_Zero is set in flags */
> -		void *p;	/* Generic pointer */
> +		/** Double value when MEM type is MEM_TYPE_DOUBLE. */
> +		double r;

5. When I talked about fixing a comment location, I meant that single
comment you needed to change anyway. But ok, lets change all alongside.

> +		/** Negative integer value when MEM type is MEM_TYPE_INT. */
> +		i64 i;
> +		/** Unsigned integer value when MEM type is MEM_TYPE_UINT. */
> +		uint64_t u;
> +		/** Boolean value when MEM type is MEM_TYPE_BOOL. */
> +		bool b;

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

* Re: [Tarantool-patches] [PATCH v2 3/3] sql: replace MEM-type flags by enum mem_type
  2021-04-29 21:09   ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-05-17 12:18     ` Mergen Imeev via Tarantool-patches
  2021-05-17 12:34       ` Mergen Imeev via Tarantool-patches
  2021-05-21 18:59       ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 2 replies; 11+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-05-17 12:18 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

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

On Thu, Apr 29, 2021 at 11:09:43PM +0200, Vladislav Shpilevoy wrote:
> I appreciate the work you did here!
> 
> >>>  	mem->field_type = FIELD_TYPE_VARBINARY;
> >>>  	return 0;
> >>>  }> @@ -1168,9 +1229,9 @@ mem_get_bin(const struct Mem *mem, const char **s)
> >>>  int
> >>>  mem_len(const struct Mem *mem, uint32_t *len)
> >>>  {
> >>> -	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
> >>> +	if (mem->type != MEM_STR && mem->type != MEM_BIN)
> >>>  		return -1;
> >>
> >> 8. Is it -1 for MAP and ARRAY intentionally? Previously they
> >> were included into MEM_Blob.
> >>
> >> I see mem_concat() didn't check for subtypes. Does it mean we
> >> could concat two MP_ARRAYs into something invalid? If we could,
> >> your patch probably just fixed it. Could you check and add a
> >> test if so?
> >>
> > Fixed. You were right, it is actually possible to concatenate two maps, map and
> > array, map and varbinary and so on. I returned ability to do this. Should I add
> > a test?
> 
> That behaviour is not correct, and should raise an error I think. You
> can add a test, but if it works now, then it should have a comment
> saying that this test is bad and must start failing in the future. And
> now you might test it just to ensure it does not crash anywhere.
> 
I removed ability to compare something with MAP/ARRAY and prohibited to
concatenate values of type MAP and ARRAY. I did not add a test, since currently
behasiour of MAP and ARRAY is not properly defined. I described this in the
previous letter.

> >>> @@ -114,175 +120,153 @@ struct Mem {
> >>
> >> <...>
> >>
> >>>  static inline bool
> >>>  mem_is_agg(const struct Mem *mem)
> >>>  {
> >>> -	return (mem->flags & MEM_Agg) != 0;
> >>> +	return mem->type == MEM_AGG;
> >>>  }
> >>>  
> >>>  static inline bool
> >>>  mem_is_bytes(const struct Mem *mem)
> >>>  {
> >>> -	return (mem->flags & (MEM_Blob | MEM_Str)) != 0;
> >>> +	enum mem_type type = mem->type;
> >>> +	return type == MEM_BIN || type == MEM_MAP || type == MEM_ARRAY ||
> >>> +	       type == MEM_STR;
> >>>  }
> >> 15. It was good when we could check several rare cases at one
> >> branch. Maybe you could preserve the bitwise structure of the
> >> types? Then we could keep the bit operations and save a few
> >> branches. You didn't think of it, or do we go for normal numbers
> >> intentionally?
> >>
> > I did it, although I'm not sure if this is the right choice: although it makes
> > our code more compact in some cases, it makes it a little less readable.
> 
> I just realized another issue which you will get with UUID and DECIMAL. They
> are not present in mp_type, so there won't be a direct mapping anyway. They
> are encoded as MP_EXT + MP_UUID/MP_DECIMAL. It fits into a 64bit integer,
> but it is a binary header, and is not in mp_type value range. The same for
> MEM_TYPE_PTR, MEM_TYPE_FRAME, and other SQL-specific.
> 
> See 5 comments below.
> 
> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index b6ff6397f..4f189cac4 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -89,39 +89,36 @@ mem_str(const struct Mem *mem)
> 
> <...>
> 
> >  static inline void
> >  mem_clear(struct Mem *mem)
> >  {
> > -	if ((mem->flags & (MEM_Agg | MEM_Dyn | MEM_Frame)) != 0) {
> > -		if ((mem->flags & MEM_Agg) != 0)
> > -			sql_vdbemem_finalize(mem, mem->u.func);
> > -		assert((mem->flags & MEM_Agg) == 0);
> > -		if ((mem->flags & MEM_Dyn) != 0) {
> > -			assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
> > -			mem->xDel((void *)mem->z);
> > -		} else if ((mem->flags & MEM_Frame) != 0) {
> > -			struct VdbeFrame *frame = mem->u.pFrame;
> > -			frame->pParent = frame->v->pDelFrame;
> > -			frame->v->pDelFrame = frame;
> > -		}
> > -	}
> > -	mem->flags = MEM_Null;
> > +	if (mem->type == MEM_TYPE_AGG) {
> > +		sql_vdbemem_finalize(mem, mem->u.func);
> > +		assert(mem->type != MEM_TYPE_AGG);
> > +	} else if (mem->type == MEM_TYPE_FRAME) {
> > +		struct VdbeFrame *frame = mem->u.pFrame;
> > +		frame->pParent = frame->v->pDelFrame;
> > +		frame->v->pDelFrame = frame;
> > +	} else if ((mem->flags & MEM_Dyn) != 0) {
> > +		assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
> > +		mem->xDel((void *)mem->z);
> > +	}
> 
> 1. With the new bitwise structure you could reduce the diff.
> For example, here there was one 'if' for a non-common case of
> something allocated dynamically. Now there are 3 'if's.
> 
Fixed, still now there is two conditions in if instead of one - one condition
for field type and one for field flags.

> > +	mem->type = MEM_TYPE_NULL;
> > +	mem->flags = 0;
> >  	mem->field_type = field_type_MAX;
> >  }
> > @@ -268,11 +271,13 @@ mem_set_str0_allocated(struct Mem *mem, char *value)
> >  int
> >  mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
> >  {
> > -	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0 && mem->z == value) {
> > +	if ((mem->type == MEM_TYPE_STR ||
> > +	     mem->type == MEM_TYPE_BIN) && mem->z == value) {
> 
> 2. The only reason I proposed to keep the types in their
> own bits was to reduce number of the branches in the 'if's.
> So as you could write flags & (MEM_TYPE_STR | MEM_TYPE_BIN) != 0
> for checking for multiple types instead of using || which adds
> an implicit branch.
> 
Sorry, I wasn't able to fully interpret this. I think now I understand.

> The same in mem_copy_bin(), mem_to_int(), mem_to_int_precise(),
> mem_to_double(), mem_to_number(), end of mem_cast_explicit()
> (the FIELD_TYPE_SCALAR case), in a few places in mem_cast_implicit(),
> mem_cast_implicit_old(), mem_get_int(), mem_get_uint(), try_return_null(),
> mem_concat(), get_number(), mem_cmp_bin(), sqlVdbeCheckMemInvariants(),
> end of memTracePrint(). Maybe more which I missed.
> 
I think I fixed the problem in all these functions and in some other.

> >  		/* Own value, but might be ephemeral. Make it own if so. */
> >  		if (sqlVdbeMemGrow(mem, len, 1) != 0)
> >  			return -1;
> > -		mem->flags = MEM_Str;
> > +		mem->type = MEM_TYPE_STR;
> > +		mem->flags = 0;
> >  		mem->field_type = FIELD_TYPE_STRING;
> >  		return 0;
> >  	}
> > @@ -2258,40 +2330,37 @@ sqlVdbeMemTooBig(Mem * p)
> >  int
> >  sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
> >  {
> > -	int f1, f2;
> >  	int res;
> > -	int combined_flags;
> >  
> > -	f1 = pMem1->flags;
> > -	f2 = pMem2->flags;
> > -	combined_flags = f1 | f2;
> > +	enum mem_type type1 = pMem1->type;
> > +	enum mem_type type2 = pMem2->type;
> >  
> >  	/* If one value is NULL, it is less than the other. If both values
> >  	 * are NULL, return 0.
> >  	 */
> > -	if (combined_flags & MEM_Null) {
> > -		return (f2 & MEM_Null) - (f1 & MEM_Null);
> > -	}
> > +	if (type1 == MEM_TYPE_NULL || type2 == MEM_TYPE_NULL)
> 
> 3. With the bitwise types you could have combined_types and
> keep only one branch in such places.
> 
Fixed.

> > +		return (int)(type2 == MEM_TYPE_NULL) -
> > +		       (int)(type1 == MEM_TYPE_NULL);
> >  
> > diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> > index 6fc15617d..a4a0d2223 100644
> > --- a/src/box/sql/mem.h
> > +++ b/src/box/sql/mem.h
> > @@ -37,73 +37,78 @@ struct region;
> >  struct mpstream;
> >  struct VdbeFrame;
> >  
> > -/*
> > - * Internally, the vdbe manipulates nearly all SQL values as Mem
> > - * structures. Each Mem struct may cache multiple representations (string,
> > - * integer etc.) of the same value.
> > - */
> > +enum mem_type {
> > +	MEM_TYPE_NULL = 1u << MP_NIL,
> > +	MEM_TYPE_UINT = 1u << MP_UINT,
> > +	MEM_TYPE_INT = 1u << MP_INT,
> > +	MEM_TYPE_STR = 1u << MP_STR,
> > +	MEM_TYPE_BIN = 1u << MP_BIN,
> > +	MEM_TYPE_ARRAY = 1u << MP_ARRAY,
> > +	MEM_TYPE_MAP = 1u << MP_MAP,
> > +	MEM_TYPE_BOOL = 1u << MP_BOOL,
> > +	MEM_TYPE_FLOAT = 1u << MP_FLOAT,
> > +	MEM_TYPE_DOUBLE = 1u << MP_DOUBLE,
> > +	MEM_TYPE_INVALID = 1u << MP_EXT,
> > +	MEM_TYPE_FRAME = 1u << (MP_EXT + 1),
> > +	MEM_TYPE_PTR = 1u << (MP_EXT + 2),
> > +	MEM_TYPE_AGG = 1u << (MP_EXT + 3),
> 
> 4. Why do you stick to mp_types? Why can't have your own
> bits? Anyway I don't see now any easy conversions between
> mem_type and mp_type. It is also kind of incorrect because
> MEM_TYPE_PTR = MP_EXT + 2 is not a pointer in MessagePack,
> and the same for the other extensions.
> 
Fixed.

> > +};
> > +
> > +/** Internally, the vdbe manipulates nearly all SQL values as Mem structures. */
> >  struct Mem {
> >  	union MemValue {
> > -		double r;	/* Real value used when MEM_Real is set in flags */
> > -		i64 i;		/* Integer value used when MEM_Int is set in flags */
> > -		uint64_t u;	/* Unsigned integer used when MEM_UInt is set. */
> > -		bool b;         /* Boolean value used when MEM_Bool is set in flags */
> > -		int nZero;	/* Used when bit MEM_Zero is set in flags */
> > -		void *p;	/* Generic pointer */
> > +		/** Double value when MEM type is MEM_TYPE_DOUBLE. */
> > +		double r;
> 
> 5. When I talked about fixing a comment location, I meant that single
> comment you needed to change anyway. But ok, lets change all alongside.
> 
I decided to revert this change along with removing uTemp and pFiller fields. I
will do all these changes in another patch, and there will be other changes that
were mentioned in discussion #6051.

I still added a comment for new field.

> > +		/** Negative integer value when MEM type is MEM_TYPE_INT. */
> > +		i64 i;
> > +		/** Unsigned integer value when MEM type is MEM_TYPE_UINT. */
> > +		uint64_t u;
> > +		/** Boolean value when MEM type is MEM_TYPE_BOOL. */
> > +		bool b;


Diff:

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 49bf4ae96..f855c111f 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -96,26 +96,31 @@ mem_create(struct Mem *mem)
 	mem->z = NULL;
 	mem->zMalloc = NULL;
 	mem->szMalloc = 0;
+	mem->uTemp = 0;
 	mem->db = sql_get();
 	mem->xDel = NULL;
 #ifdef SQL_DEBUG
 	mem->pScopyFrom = NULL;
+	mem->pFiller = NULL;
 #endif
 }
 
 static inline void
 mem_clear(struct Mem *mem)
 {
-	if (mem->type == MEM_TYPE_AGG) {
-		sql_vdbemem_finalize(mem, mem->u.func);
+	if ((mem->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0 ||
+	    (mem->flags & MEM_Dyn) != 0) {
+		if (mem->type == MEM_TYPE_AGG)
+			sql_vdbemem_finalize(mem, mem->u.func);
 		assert(mem->type != MEM_TYPE_AGG);
-	} else if (mem->type == MEM_TYPE_FRAME) {
-		struct VdbeFrame *frame = mem->u.pFrame;
-		frame->pParent = frame->v->pDelFrame;
-		frame->v->pDelFrame = frame;
-	} else if ((mem->flags & MEM_Dyn) != 0) {
-		assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
-		mem->xDel((void *)mem->z);
+		if ((mem->flags & MEM_Dyn) != 0) {
+			assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
+			mem->xDel((void *)mem->z);
+		} else if (mem->type == MEM_TYPE_FRAME) {
+			struct VdbeFrame *frame = mem->u.pFrame;
+			frame->pParent = frame->v->pDelFrame;
+			frame->v->pDelFrame = frame;
+		}
 	}
 	mem->type = MEM_TYPE_NULL;
 	mem->flags = 0;
@@ -271,8 +276,8 @@ mem_set_str0_allocated(struct Mem *mem, char *value)
 int
 mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
 {
-	if ((mem->type == MEM_TYPE_STR ||
-	     mem->type == MEM_TYPE_BIN) && mem->z == value) {
+	if (((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) &&
+	    mem->z == value) {
 		/* Own value, but might be ephemeral. Make it own if so. */
 		if (sqlVdbeMemGrow(mem, len, 1) != 0)
 			return -1;
@@ -363,8 +368,8 @@ mem_set_bin_allocated(struct Mem *mem, char *value, uint32_t size)
 int
 mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
 {
-	if ((mem->type == MEM_TYPE_STR ||
-	     mem->type == MEM_TYPE_BIN) && mem->z == value) {
+	if (((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) &&
+	    mem->z == value) {
 		/* Own value, but might be ephemeral. Make it own if so. */
 		if (sqlVdbeMemGrow(mem, size, 1) != 0)
 			return -1;
@@ -520,7 +525,7 @@ mem_set_null_clear(struct Mem *mem)
 static inline int
 int_to_double(struct Mem *mem)
 {
-	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
+	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
 	double d;
 	if (mem->type == MEM_TYPE_UINT)
 		d = (double)mem->u.u;
@@ -536,7 +541,7 @@ int_to_double(struct Mem *mem)
 static inline int
 int_to_str0(struct Mem *mem)
 {
-	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
+	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
 	const char *str;
 	if (mem->type == MEM_TYPE_UINT)
 		str = tt_sprintf("%llu", mem->u.u);
@@ -548,7 +553,7 @@ int_to_str0(struct Mem *mem)
 static inline int
 int_to_bool(struct Mem *mem)
 {
-	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
+	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
 	mem->u.b = mem->u.i != 0;
 	mem->type = MEM_TYPE_BOOL;
 	assert(mem->flags == 0);
@@ -635,7 +640,7 @@ bin_to_str0(struct Mem *mem)
 static inline int
 bytes_to_int(struct Mem *mem)
 {
-	assert(mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN);
+	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
 	bool is_neg;
 	int64_t i;
 	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
@@ -647,7 +652,7 @@ bytes_to_int(struct Mem *mem)
 static inline int
 bytes_to_uint(struct Mem *mem)
 {
-	assert(mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN);
+	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
 	bool is_neg;
 	int64_t i;
 	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
@@ -661,7 +666,7 @@ bytes_to_uint(struct Mem *mem)
 static inline int
 bytes_to_double(struct Mem *mem)
 {
-	assert(mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN);
+	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
 	double d;
 	if (sqlAtoF(mem->z, &d, mem->n) == 0)
 		return -1;
@@ -807,14 +812,13 @@ int
 mem_to_int(struct Mem *mem)
 {
 	assert(mem->type < MEM_TYPE_INVALID);
-	enum mem_type type = mem->type;
-	if (type == MEM_TYPE_INT || type == MEM_TYPE_UINT)
+	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 		return 0;
-	if (type == MEM_TYPE_STR || type == MEM_TYPE_BIN)
+	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
 		return bytes_to_int(mem);
-	if (type == MEM_TYPE_DOUBLE)
+	if (mem->type == MEM_TYPE_DOUBLE)
 		return double_to_int(mem);
-	if (type == MEM_TYPE_BOOL)
+	if (mem->type == MEM_TYPE_BOOL)
 		return bool_to_int(mem);
 	return -1;
 }
@@ -823,12 +827,11 @@ int
 mem_to_int_precise(struct Mem *mem)
 {
 	assert(mem->type < MEM_TYPE_INVALID);
-	enum mem_type type = mem->type;
-	if (type == MEM_TYPE_INT || type == MEM_TYPE_UINT)
+	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 		return 0;
-	if (type == MEM_TYPE_STR)
+	if (mem->type == MEM_TYPE_STR)
 		return bytes_to_int(mem);
-	if (type == MEM_TYPE_DOUBLE)
+	if (mem->type == MEM_TYPE_DOUBLE)
 		return double_to_int_precise(mem);
 	return -1;
 }
@@ -837,12 +840,11 @@ int
 mem_to_double(struct Mem *mem)
 {
 	assert(mem->type < MEM_TYPE_INVALID);
-	enum mem_type type = mem->type;
-	if (type == MEM_TYPE_DOUBLE)
+	if (mem->type == MEM_TYPE_DOUBLE)
 		return 0;
-	if (type == MEM_TYPE_INT || type == MEM_TYPE_UINT)
+	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 		return int_to_double(mem);
-	if (type == MEM_TYPE_STR)
+	if (mem->type == MEM_TYPE_STR)
 		return bytes_to_double(mem);
 	return -1;
 }
@@ -855,7 +857,7 @@ mem_to_number(struct Mem *mem)
 		return 0;
 	if (mem->type == MEM_TYPE_BOOL)
 		return bool_to_int(mem);
-	if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN) {
+	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) {
 		if (bytes_to_int(mem) == 0)
 			return 0;
 		return bytes_to_double(mem);
@@ -966,7 +968,7 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
 	case FIELD_TYPE_NUMBER:
 		return mem_to_number(mem);
 	case FIELD_TYPE_SCALAR:
-		if (mem->type == MEM_TYPE_MAP || mem->type == MEM_TYPE_ARRAY)
+		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
 			return -1;
 		return 0;
 	default:
@@ -996,11 +998,11 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
 	case FIELD_TYPE_DOUBLE:
 		if (mem->type == MEM_TYPE_DOUBLE)
 			return 0;
-		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
+		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 			return int_to_double(mem);
 		return -1;
 	case FIELD_TYPE_INTEGER:
-		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
+		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 			return 0;
 		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_int(mem);
@@ -1010,7 +1012,8 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
 			return 0;
 		return -1;
 	case FIELD_TYPE_VARBINARY:
-		if (mem->type != MEM_TYPE_STR && mem_is_bytes(mem))
+		if ((mem->type & (MEM_TYPE_BIN | MEM_TYPE_MAP |
+				  MEM_TYPE_ARRAY)) != 0)
 			return 0;
 		return -1;
 	case FIELD_TYPE_NUMBER:
@@ -1026,7 +1029,7 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
 			return 0;
 		return -1;
 	case FIELD_TYPE_SCALAR:
-		if (mem->type == MEM_TYPE_MAP || mem->type == MEM_TYPE_ARRAY)
+		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
 			return -1;
 		return 0;
 	case FIELD_TYPE_ANY:
@@ -1052,9 +1055,9 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type)
 			return bytes_to_uint(mem);
 		return -1;
 	case FIELD_TYPE_STRING:
-		if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN)
+		if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
 			return 0;
-		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
+		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 			return int_to_str0(mem);
 		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_str0(mem);
@@ -1062,13 +1065,13 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type)
 	case FIELD_TYPE_DOUBLE:
 		if (mem->type == MEM_TYPE_DOUBLE)
 			return 0;
-		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
+		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 			return int_to_double(mem);
 		if (mem->type == MEM_TYPE_STR)
 			return bin_to_str(mem);
 		return -1;
 	case FIELD_TYPE_INTEGER:
-		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
+		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 			return 0;
 		if (mem->type == MEM_TYPE_STR)
 			return bytes_to_int(mem);
@@ -1098,7 +1101,7 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type)
 			return 0;
 		return -1;
 	case FIELD_TYPE_SCALAR:
-		if (mem->type == MEM_TYPE_MAP || mem->type == MEM_TYPE_ARRAY)
+		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
 			return -1;
 		return 0;
 	default:
@@ -1120,7 +1123,7 @@ mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
 		*is_neg = false;
 		return 0;
 	}
-	if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN)
+	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
 		return sql_atoi64(mem->z, i, is_neg, mem->n);
 	if (mem->type == MEM_TYPE_DOUBLE) {
 		double d = mem->u.r;
@@ -1148,7 +1151,7 @@ mem_get_uint(const struct Mem *mem, uint64_t *u)
 		*u = mem->u.u;
 		return 0;
 	}
-	if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN) {
+	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) {
 		bool is_neg;
 		if (sql_atoi64(mem->z, (int64_t *)u, &is_neg, mem->n) != 0 ||
 		    is_neg)
@@ -1305,7 +1308,7 @@ try_return_null(const struct Mem *a, const struct Mem *b, struct Mem *result,
 {
 	mem_clear(result);
 	result->field_type = type;
-	return a->type == MEM_TYPE_NULL || b->type == MEM_TYPE_NULL;
+	return ((a->type | b->type) & MEM_TYPE_NULL) != 0;
 }
 
 int
@@ -1316,7 +1319,7 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 		if (try_return_null(a, b, result, FIELD_TYPE_STRING))
 			return 0;
 	} else {
-		if (a->type == MEM_TYPE_NULL || b->type == MEM_TYPE_NULL) {
+		if (((a->type | b->type) & MEM_TYPE_NULL) != 0) {
 			mem_clear(a);
 			result->field_type = FIELD_TYPE_STRING;
 			return 0;
@@ -1324,18 +1327,19 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 	}
 
 	/* Concatenation operation can be applied only to strings and blobs. */
-	if (!mem_is_bytes(b)) {
+	if (((b->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)) {
 		diag_set(ClientError, ER_INCONSISTENT_TYPES,
 			 "text or varbinary", mem_type_to_str(b));
 		return -1;
 	}
-	if (!mem_is_bytes(a)) {
+	if (((a->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)) {
 		diag_set(ClientError, ER_INCONSISTENT_TYPES,
 			 "text or varbinary", mem_type_to_str(a));
 		return -1;
 	}
 
-	if (b->type != a->type && ((b->type | a->type) & MEM_TYPE_STR) != 0) {
+	/* Moreover, both operands must be of the same type. */
+	if (b->type != a->type) {
 		diag_set(ClientError, ER_INCONSISTENT_TYPES,
 			 mem_type_to_str(a), mem_type_to_str(b));
 		return -1;
@@ -1393,7 +1397,7 @@ get_number(const struct Mem *mem, struct sql_num *number)
 		number->is_neg = false;
 		return 0;
 	}
-	if (mem->type != MEM_TYPE_STR && mem->type != MEM_TYPE_BIN)
+	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)
 		return -1;
 	if (sql_atoi64(mem->z, &number->i, &number->is_neg, mem->n) == 0) {
 		number->type = number->is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
@@ -1428,16 +1432,16 @@ arithmetic_prepare(const struct Mem *left, const struct Mem *right,
 		return -1;
 	}
 	assert(a->type != 0 && b->type != 0);
-	if (a->type == MEM_TYPE_DOUBLE && b->type != MEM_TYPE_DOUBLE) {
+	if (a->type == b->type || ((a->type | b->type) & MEM_TYPE_DOUBLE) == 0)
+		return 0;
+	if (a->type == MEM_TYPE_DOUBLE) {
 		b->d = b->type == MEM_TYPE_INT ? (double)b->i : (double)b->u;
 		b->type = MEM_TYPE_DOUBLE;
 		return 0;
 	}
-	if (a->type != MEM_TYPE_DOUBLE && b->type == MEM_TYPE_DOUBLE) {
-		a->d = a->type == MEM_TYPE_INT ? (double)a->i : (double)a->u;
-		a->type = MEM_TYPE_DOUBLE;
-		return 0;
-	}
+	assert(b->type == MEM_TYPE_DOUBLE);
+	a->d = a->type == MEM_TYPE_INT ? (double)a->i : (double)a->u;
+	a->type = MEM_TYPE_DOUBLE;
 	return 0;
 }
 
@@ -1735,7 +1739,7 @@ mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
 int
 mem_cmp_bin(const struct Mem *a, const struct Mem *b, int *result)
 {
-	if (!mem_is_bin(a) || !mem_is_bin(b))
+	if ((a->type & b->type & MEM_TYPE_BIN) == 0)
 		return -1;
 	int an = a->n;
 	int bn = b->n;
@@ -2025,7 +2029,7 @@ sqlVdbeCheckMemInvariants(Mem * p)
 	 *   (3) An ephemeral string or blob
 	 *   (4) A static string or blob
 	 */
-	if ((p->type == MEM_TYPE_STR || p->type == MEM_TYPE_BIN) && p->n > 0) {
+	if ((p->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0 && p->n > 0) {
 		assert(((p->szMalloc > 0 && p->z == p->zMalloc) ? 1 : 0) +
 		       ((p->flags & MEM_Dyn) != 0 ? 1 : 0) +
 		       ((p->flags & MEM_Ephem) != 0 ? 1 : 0) +
@@ -2143,7 +2147,7 @@ memTracePrint(Mem *p)
 		char zBuf[200];
 		sqlVdbeMemPrettyPrint(p, zBuf);
 		printf(" %s", zBuf);
-		if (p->type == MEM_TYPE_MAP || p->type == MEM_TYPE_ARRAY)
+		if ((p->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
 			printf(" subtype=0x%02x", SQL_SUBTYPE_MSGPACK);
 		return;
 	}
@@ -2338,11 +2342,11 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
 	/* If one value is NULL, it is less than the other. If both values
 	 * are NULL, return 0.
 	 */
-	if (type1 == MEM_TYPE_NULL || type2 == MEM_TYPE_NULL)
+	if (((type1 | type2) & MEM_TYPE_NULL) != 0)
 		return (int)(type2 == MEM_TYPE_NULL) -
 		       (int)(type1 == MEM_TYPE_NULL);
 
-	if (type1 == MEM_TYPE_BOOL || type2 == MEM_TYPE_BOOL) {
+	if (((type1 | type2) & MEM_TYPE_BOOL) != 0) {
 		if (type1 == MEM_TYPE_BOOL && type2 == MEM_TYPE_BOOL) {
 			if (pMem1->u.b == pMem2->u.b)
 				return 0;
@@ -2357,7 +2361,8 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
 
 	/* At least one of the two values is a number
 	 */
-	if (mem_is_num(pMem1) || mem_is_num(pMem2)) {
+	if (((type1 | type2) &
+	     (MEM_TYPE_INT | MEM_TYPE_UINT | MEM_TYPE_DOUBLE)) != 0) {
 		if (!mem_is_num(pMem1))
 			return +1;
 		if (!mem_is_num(pMem2))
@@ -2369,7 +2374,7 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
 	/* If one value is a string and the other is a blob, the string is less.
 	 * If both are strings, compare using the collating functions.
 	 */
-	if (type1 == MEM_TYPE_STR || type2 == MEM_TYPE_STR) {
+	if (((type1 | type2) & MEM_TYPE_STR) != 0) {
 		if (type1 != MEM_TYPE_STR) {
 			return 1;
 		}
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 59f006dd6..29d373cfd 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -38,73 +38,65 @@ struct mpstream;
 struct VdbeFrame;
 
 enum mem_type {
-	MEM_TYPE_NULL = 1u << MP_NIL,
-	MEM_TYPE_UINT = 1u << MP_UINT,
-	MEM_TYPE_INT = 1u << MP_INT,
-	MEM_TYPE_STR = 1u << MP_STR,
-	MEM_TYPE_BIN = 1u << MP_BIN,
-	MEM_TYPE_ARRAY = 1u << MP_ARRAY,
-	MEM_TYPE_MAP = 1u << MP_MAP,
-	MEM_TYPE_BOOL = 1u << MP_BOOL,
-	MEM_TYPE_FLOAT = 1u << MP_FLOAT,
-	MEM_TYPE_DOUBLE = 1u << MP_DOUBLE,
-	MEM_TYPE_INVALID = 1u << MP_EXT,
-	MEM_TYPE_FRAME = 1u << (MP_EXT + 1),
-	MEM_TYPE_PTR = 1u << (MP_EXT + 2),
-	MEM_TYPE_AGG = 1u << (MP_EXT + 3),
+	MEM_TYPE_NULL		= 1,
+	MEM_TYPE_UINT		= 1 << 1,
+	MEM_TYPE_INT		= 1 << 2,
+	MEM_TYPE_STR		= 1 << 3,
+	MEM_TYPE_BIN		= 1 << 4,
+	MEM_TYPE_ARRAY		= 1 << 5,
+	MEM_TYPE_MAP		= 1 << 6,
+	MEM_TYPE_BOOL		= 1 << 7,
+	MEM_TYPE_FLOAT		= 1 << 8,
+	MEM_TYPE_DOUBLE		= 1 << 9,
+	MEM_TYPE_INVALID	= 1 << 10,
+	MEM_TYPE_FRAME		= 1 << 11,
+	MEM_TYPE_PTR		= 1 << 12,
+	MEM_TYPE_AGG		= 1 << 13,
 };
 
-/** Internally, the vdbe manipulates nearly all SQL values as Mem structures. */
+/*
+ * Internally, the vdbe manipulates nearly all SQL values as Mem
+ * structures. Each Mem struct may cache multiple representations (string,
+ * integer etc.) of the same value.
+ */
 struct Mem {
 	union MemValue {
-		/** Double value when MEM type is MEM_TYPE_DOUBLE. */
-		double r;
-		/** Negative integer value when MEM type is MEM_TYPE_INT. */
-		i64 i;
-		/** Unsigned integer value when MEM type is MEM_TYPE_UINT. */
-		uint64_t u;
-		/** Boolean value when MEM type is MEM_TYPE_BOOL. */
-		bool b;
+		double r;	/* Real value used when MEM_Real is set in flags */
+		i64 i;		/* Integer value used when MEM_Int is set in flags */
+		uint64_t u;	/* Unsigned integer used when MEM_UInt is set. */
+		bool b;         /* Boolean value used when MEM_Bool is set in flags */
+		int nZero;	/* Used when bit MEM_Zero is set in flags */
+		void *p;	/* Generic pointer */
 		/**
-		 * Number of zeroes when MEM type is MEM_TYPE_BIN and MEM_Zero
-		 * flag is set.
+		 * A pointer to function implementation.
+		 * Used only when flags==MEM_Agg.
 		 */
-		int nZero;
-		/** Generic pointer when MEM type is MEM_TYPE_PTR. */
-		void *p;
-		/** Pointer to function when MEM type is MEM_TYPE_AGG. */
 		struct func *func;
-		/** Pointer to frame when MEM type is MEM_TYPE_FRAME. */
-		struct VdbeFrame *pFrame;
+		struct VdbeFrame *pFrame;	/* Used when flags==MEM_Frame */
 	} u;
 	/** Type of the value this MEM contains. */
 	enum mem_type type;
-	/** Additional information for MEM types. */
-	u32 flags;
+	u32 flags;		/* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
+	/** Subtype for this value. */
+	enum sql_subtype subtype;
 	/**
-	 * Field type of the value this MEM contains. Should be field_type_MAX
-	 * or field type compatible with MEM type.
+	 * If value is fetched from tuple, then this property
+	 * contains type of corresponding space's field. If it's
+	 * value field_type_MAX then we can rely on on format
+	 * (msgpack) type which is represented by 'flags'.
 	 */
 	enum field_type field_type;
-	/**
-	 * The size in bytes for a STRING, VARBINARY, MAP, or ARRAY value. For a
-	 * STRING, if MEM_Term is set, the actual size is n + 1, since there is
-	 * trailing '\0'.
-	 */
-	int n;
-	/** STRING, VARBINARY, MAP, or ARRAY if set to the appropriate type. */
-	char *z;
-	/** Memory, allocated by MEM. */
-	char *zMalloc;
-	/** Size of memory, allocated by MEM. */
-	int szMalloc;
-	/** Database that contains this MEM. */
-	struct sql *db;
-	/** Destructor in case MEM_Dyn is set. */
-	void (*xDel) (void *);
+	int n;			/* size (in bytes) of string value, excluding trailing '\0' */
+	char *z;		/* String or BLOB value */
+	/* ShallowCopy only needs to copy the information above */
+	char *zMalloc;		/* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */
+	int szMalloc;		/* Size of the zMalloc allocation */
+	u32 uTemp;		/* Transient storage for serial_type in OP_MakeRecord */
+	sql *db;		/* The associated database connection */
+	void (*xDel) (void *);	/* Destructor for Mem.z - only valid if MEM_Dyn */
 #ifdef SQL_DEBUG
-	/** This Mem is a shallow copy of pScopyFrom. */
-	struct Mem *pScopyFrom;
+	Mem *pScopyFrom;	/* This Mem is a shallow copy of pScopyFrom */
+	void *pFiller;		/* So that sizeof(Mem) is a multiple of 8 */
 #endif
 };
 
@@ -263,7 +255,7 @@ mem_is_same_type(const struct Mem *mem1, const struct Mem *mem2)
 static inline bool
 mem_is_any_null(const struct Mem *mem1, const struct Mem *mem2)
 {
-	return mem1->type == MEM_TYPE_NULL || mem2->type == MEM_TYPE_NULL;
+	return ((mem1->type| mem2->type) & MEM_TYPE_NULL) != 0;
 }
 
 /**
@@ -950,10 +942,8 @@ int sqlVdbeMemTooBig(Mem *);
 /* Return TRUE if Mem X contains dynamically allocated content - anything
  * that needs to be deallocated to avoid a leak.
  */
-#define VdbeMemDynamic(X) ((X)->type == MEM_TYPE_AGG || \
-			   (X)->type == MEM_TYPE_FRAME || \
-			   ((X)->flags & MEM_Dyn) != 0)
-
+#define VdbeMemDynamic(X) (((X)->flags & MEM_Dyn) != 0 ||\
+			   ((X)->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0)
 
 int sqlMemCompare(const Mem *, const Mem *, const struct coll *);
 




New patch:


commit a17b49b00a6241d5c240da4ff9ec24379b7e0431
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Fri Apr 16 18:06:31 2021 +0300

    sql: replace MEM-type flags by enum mem_type
    
    This patch moves MEM types from the 'u32 flags' field to the new
    'enum mem_type type' field. Now, we can be sure that only one type is
    set for MEM. In addition, it is now easier to distinguish MAP and ARRAY
    from VARBINARY, and this makes it easier to add extension types - UUID
    and DECIMAL.
    
    Closes #4906

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index b6ff6397f..f855c111f 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -60,26 +60,26 @@ const char *
 mem_str(const struct Mem *mem)
 {
 	char buf[BUF_SIZE];
-	switch (mem->flags & MEM_PURE_TYPE_MASK) {
-	case MEM_Null:
+	switch (mem->type) {
+	case MEM_TYPE_NULL:
 		return "NULL";
-	case MEM_Str:
+	case MEM_TYPE_STR:
 		if ((mem->flags & MEM_Term) != 0)
 			return mem->z;
 		return tt_cstr(mem->z, mem->n);
-	case MEM_Int:
+	case MEM_TYPE_INT:
 		return tt_sprintf("%lld", mem->u.i);
-	case MEM_UInt:
+	case MEM_TYPE_UINT:
 		return tt_sprintf("%llu", mem->u.u);
-	case MEM_Real:
+	case MEM_TYPE_DOUBLE:
 		sql_snprintf(BUF_SIZE, &buf[0], "%!.15g", mem->u.r);
 		return tt_sprintf("%s", buf);
-	case MEM_Blob:
-		if ((mem->flags & MEM_Subtype) == 0)
-			return "varbinary";
-		assert(mem->subtype == SQL_SUBTYPE_MSGPACK);
+	case MEM_TYPE_BIN:
+		return "varbinary";
+	case MEM_TYPE_MAP:
+	case MEM_TYPE_ARRAY:
 		return mp_str(mem->z);
-	case MEM_Bool:
+	case MEM_TYPE_BOOL:
 		return mem->u.b ? "TRUE" : "FALSE";
 	default:
 		return "unknown";
@@ -89,8 +89,8 @@ mem_str(const struct Mem *mem)
 void
 mem_create(struct Mem *mem)
 {
-	mem->flags = MEM_Null;
-	mem->subtype = SQL_SUBTYPE_NO;
+	mem->type = MEM_TYPE_NULL;
+	mem->flags = 0;
 	mem->field_type = field_type_MAX;
 	mem->n = 0;
 	mem->z = NULL;
@@ -108,20 +108,22 @@ mem_create(struct Mem *mem)
 static inline void
 mem_clear(struct Mem *mem)
 {
-	if ((mem->flags & (MEM_Agg | MEM_Dyn | MEM_Frame)) != 0) {
-		if ((mem->flags & MEM_Agg) != 0)
+	if ((mem->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0 ||
+	    (mem->flags & MEM_Dyn) != 0) {
+		if (mem->type == MEM_TYPE_AGG)
 			sql_vdbemem_finalize(mem, mem->u.func);
-		assert((mem->flags & MEM_Agg) == 0);
+		assert(mem->type != MEM_TYPE_AGG);
 		if ((mem->flags & MEM_Dyn) != 0) {
 			assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
 			mem->xDel((void *)mem->z);
-		} else if ((mem->flags & MEM_Frame) != 0) {
+		} else if (mem->type == MEM_TYPE_FRAME) {
 			struct VdbeFrame *frame = mem->u.pFrame;
 			frame->pParent = frame->v->pDelFrame;
 			frame->v->pDelFrame = frame;
 		}
 	}
-	mem->flags = MEM_Null;
+	mem->type = MEM_TYPE_NULL;
+	mem->flags = 0;
 	mem->field_type = field_type_MAX;
 }
 
@@ -149,7 +151,8 @@ mem_set_int(struct Mem *mem, int64_t value, bool is_neg)
 {
 	mem_clear(mem);
 	mem->u.i = value;
-	mem->flags = is_neg ? MEM_Int : MEM_UInt;
+	mem->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_INTEGER;
 }
 
@@ -158,7 +161,8 @@ mem_set_uint(struct Mem *mem, uint64_t value)
 {
 	mem_clear(mem);
 	mem->u.u = value;
-	mem->flags = MEM_UInt;
+	mem->type = MEM_TYPE_UINT;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_UNSIGNED;
 }
 
@@ -167,7 +171,8 @@ mem_set_bool(struct Mem *mem, bool value)
 {
 	mem_clear(mem);
 	mem->u.b = value;
-	mem->flags = MEM_Bool;
+	mem->type = MEM_TYPE_BOOL;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_BOOLEAN;
 }
 
@@ -176,10 +181,11 @@ mem_set_double(struct Mem *mem, double value)
 {
 	mem_clear(mem);
 	mem->field_type = FIELD_TYPE_DOUBLE;
+	assert(mem->flags == 0);
 	if (sqlIsNaN(value))
 		return;
 	mem->u.r = value;
-	mem->flags = MEM_Real;
+	mem->type = MEM_TYPE_DOUBLE;
 }
 
 static inline void
@@ -189,7 +195,8 @@ set_str_const(struct Mem *mem, char *value, uint32_t len, int alloc_type)
 	mem_clear(mem);
 	mem->z = value;
 	mem->n = len;
-	mem->flags = MEM_Str | alloc_type;
+	mem->type = MEM_TYPE_STR;
+	mem->flags = alloc_type;
 	mem->field_type = FIELD_TYPE_STRING;
 }
 
@@ -202,7 +209,8 @@ set_str_dynamic(struct Mem *mem, char *value, uint32_t len, int alloc_type)
 	mem_destroy(mem);
 	mem->z = value;
 	mem->n = len;
-	mem->flags = MEM_Str | alloc_type;
+	mem->type = MEM_TYPE_STR;
+	mem->flags = alloc_type;
 	mem->field_type = FIELD_TYPE_STRING;
 	if (alloc_type == MEM_Dyn) {
 		mem->xDel = sql_free;
@@ -268,11 +276,13 @@ mem_set_str0_allocated(struct Mem *mem, char *value)
 int
 mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
 {
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0 && mem->z == value) {
+	if (((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) &&
+	    mem->z == value) {
 		/* Own value, but might be ephemeral. Make it own if so. */
 		if (sqlVdbeMemGrow(mem, len, 1) != 0)
 			return -1;
-		mem->flags = MEM_Str;
+		mem->type = MEM_TYPE_STR;
+		mem->flags = 0;
 		mem->field_type = FIELD_TYPE_STRING;
 		return 0;
 	}
@@ -281,7 +291,8 @@ mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
 		return -1;
 	memcpy(mem->z, value, len);
 	mem->n = len;
-	mem->flags = MEM_Str;
+	mem->type = MEM_TYPE_STR;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_STRING;
 	return 0;
 }
@@ -304,7 +315,8 @@ set_bin_const(struct Mem *mem, char *value, uint32_t size, int alloc_type)
 	mem_clear(mem);
 	mem->z = value;
 	mem->n = size;
-	mem->flags = MEM_Blob | alloc_type;
+	mem->type = MEM_TYPE_BIN;
+	mem->flags = alloc_type;
 	mem->field_type = FIELD_TYPE_VARBINARY;
 }
 
@@ -317,7 +329,8 @@ set_bin_dynamic(struct Mem *mem, char *value, uint32_t size, int alloc_type)
 	mem_destroy(mem);
 	mem->z = value;
 	mem->n = size;
-	mem->flags = MEM_Blob | alloc_type;
+	mem->type = MEM_TYPE_BIN;
+	mem->flags = alloc_type;
 	mem->field_type = FIELD_TYPE_VARBINARY;
 	if (alloc_type == MEM_Dyn) {
 		mem->xDel = sql_free;
@@ -355,11 +368,13 @@ mem_set_bin_allocated(struct Mem *mem, char *value, uint32_t size)
 int
 mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
 {
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0 && mem->z == value) {
+	if (((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) &&
+	    mem->z == value) {
 		/* Own value, but might be ephemeral. Make it own if so. */
 		if (sqlVdbeMemGrow(mem, size, 1) != 0)
 			return -1;
-		mem->flags = MEM_Blob;
+		mem->type = MEM_TYPE_BIN;
+		mem->flags = 0;
 		mem->field_type = FIELD_TYPE_VARBINARY;
 		return 0;
 	}
@@ -368,7 +383,8 @@ mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
 		return -1;
 	memcpy(mem->z, value, size);
 	mem->n = size;
-	mem->flags = MEM_Blob;
+	mem->type = MEM_TYPE_BIN;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_VARBINARY;
 	return 0;
 }
@@ -382,7 +398,8 @@ mem_set_zerobin(struct Mem *mem, int n)
 	mem->u.nZero = n;
 	mem->z = NULL;
 	mem->n = 0;
-	mem->flags = MEM_Blob | MEM_Zero;
+	mem->type = MEM_TYPE_BIN;
+	mem->flags = MEM_Zero;
 	mem->field_type = FIELD_TYPE_VARBINARY;
 }
 
@@ -390,12 +407,12 @@ static inline void
 set_msgpack_value(struct Mem *mem, char *value, uint32_t size, int alloc_type,
 		  enum field_type type)
 {
+	assert(type == FIELD_TYPE_MAP || type == FIELD_TYPE_ARRAY);
 	if (alloc_type == MEM_Ephem || alloc_type == MEM_Static)
 		set_bin_const(mem, value, size, alloc_type);
 	else
 		set_bin_dynamic(mem, value, size, alloc_type);
-	mem->flags |= MEM_Subtype;
-	mem->subtype = SQL_SUBTYPE_MSGPACK;
+	mem->type = type == FIELD_TYPE_MAP ? MEM_TYPE_MAP : MEM_TYPE_ARRAY;
 	mem->field_type = type;
 }
 
@@ -459,14 +476,16 @@ void
 mem_set_invalid(struct Mem *mem)
 {
 	mem_clear(mem);
-	mem->flags = MEM_Undefined;
+	mem->type = MEM_TYPE_INVALID;
+	assert(mem->flags == 0);
 }
 
 void
 mem_set_ptr(struct Mem *mem, void *ptr)
 {
 	mem_clear(mem);
-	mem->flags = MEM_Ptr;
+	mem->type = MEM_TYPE_PTR;
+	assert(mem->flags == 0);
 	mem->u.p = ptr;
 }
 
@@ -474,7 +493,8 @@ void
 mem_set_frame(struct Mem *mem, struct VdbeFrame *frame)
 {
 	mem_clear(mem);
-	mem->flags = MEM_Frame;
+	mem->type = MEM_TYPE_FRAME;
+	assert(mem->flags == 0);
 	mem->u.pFrame = frame;
 }
 
@@ -488,7 +508,8 @@ mem_set_agg(struct Mem *mem, struct func *func, int size)
 		return -1;
 	memset(mem->z, 0, size);
 	mem->n = size;
-	mem->flags = MEM_Agg;
+	mem->type = MEM_TYPE_AGG;
+	assert(mem->flags == 0);
 	mem->u.func = func;
 	mem->field_type = field_type_MAX;
 	return 0;
@@ -498,19 +519,21 @@ void
 mem_set_null_clear(struct Mem *mem)
 {
 	mem_clear(mem);
-	mem->flags = MEM_Null | MEM_Cleared;
+	mem->flags = MEM_Cleared;
 }
 
 static inline int
 int_to_double(struct Mem *mem)
 {
+	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
 	double d;
-	if ((mem->flags & MEM_UInt) != 0)
+	if (mem->type == MEM_TYPE_UINT)
 		d = (double)mem->u.u;
 	else
 		d = (double)mem->u.i;
 	mem->u.r = d;
-	mem->flags = MEM_Real;
+	mem->type = MEM_TYPE_DOUBLE;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_DOUBLE;
 	return 0;
 }
@@ -518,8 +541,9 @@ int_to_double(struct Mem *mem)
 static inline int
 int_to_str0(struct Mem *mem)
 {
+	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
 	const char *str;
-	if ((mem->flags & MEM_UInt) != 0)
+	if (mem->type == MEM_TYPE_UINT)
 		str = tt_sprintf("%llu", mem->u.u);
 	else
 		str = tt_sprintf("%lld", mem->u.i);
@@ -529,8 +553,10 @@ int_to_str0(struct Mem *mem)
 static inline int
 int_to_bool(struct Mem *mem)
 {
+	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
 	mem->u.b = mem->u.i != 0;
-	mem->flags = MEM_Bool;
+	mem->type = MEM_TYPE_BOOL;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_BOOLEAN;
 	return 0;
 }
@@ -538,7 +564,7 @@ int_to_bool(struct Mem *mem)
 static inline int
 str_to_str0(struct Mem *mem)
 {
-	assert((mem->flags | MEM_Str) != 0);
+	assert(mem->type == MEM_TYPE_STR);
 	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
 		return -1;
 	mem->z[mem->n] = '\0';
@@ -550,8 +576,9 @@ str_to_str0(struct Mem *mem)
 static inline int
 str_to_bin(struct Mem *mem)
 {
-	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
-		     MEM_Blob;
+	assert(mem->type == MEM_TYPE_STR);
+	mem->type = MEM_TYPE_BIN;
+	mem->flags &= ~MEM_Term;
 	mem->field_type = FIELD_TYPE_VARBINARY;
 	return 0;
 }
@@ -559,6 +586,7 @@ str_to_bin(struct Mem *mem)
 static inline int
 str_to_bool(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_STR);
 	char *str = mem->z;
 	bool b;
 	const char *str_true = "TRUE";
@@ -586,10 +614,10 @@ str_to_bool(struct Mem *mem)
 static inline int
 bin_to_str(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_BIN);
 	if (ExpandBlob(mem) != 0)
 		return -1;
-	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
-		      MEM_Str;
+	mem->type = MEM_TYPE_STR;
 	mem->field_type = FIELD_TYPE_STRING;
 	return 0;
 }
@@ -597,12 +625,14 @@ bin_to_str(struct Mem *mem)
 static inline int
 bin_to_str0(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_BIN);
 	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->type = MEM_TYPE_STR;
+	mem->flags = MEM_Term;
 	mem->field_type = FIELD_TYPE_STRING;
 	return 0;
 }
@@ -610,6 +640,7 @@ bin_to_str0(struct Mem *mem)
 static inline int
 bytes_to_int(struct Mem *mem)
 {
+	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
 	bool is_neg;
 	int64_t i;
 	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
@@ -621,6 +652,7 @@ bytes_to_int(struct Mem *mem)
 static inline int
 bytes_to_uint(struct Mem *mem)
 {
+	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
 	bool is_neg;
 	int64_t i;
 	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
@@ -634,6 +666,7 @@ bytes_to_uint(struct Mem *mem)
 static inline int
 bytes_to_double(struct Mem *mem)
 {
+	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
 	double d;
 	if (sqlAtoF(mem->z, &d, mem->n) == 0)
 		return -1;
@@ -644,16 +677,19 @@ bytes_to_double(struct Mem *mem)
 static inline int
 double_to_int(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	double d = mem->u.r;
 	if (d < 0 && d >= (double)INT64_MIN) {
 		mem->u.i = (int64_t)d;
-		mem->flags = MEM_Int;
+		mem->type = MEM_TYPE_INT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_INTEGER;
 		return 0;
 	}
 	if (d >= 0 && d < (double)UINT64_MAX) {
 		mem->u.u = (uint64_t)d;
-		mem->flags = MEM_UInt;
+		mem->type = MEM_TYPE_UINT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_UNSIGNED;
 		return 0;
 	}
@@ -663,16 +699,19 @@ double_to_int(struct Mem *mem)
 static inline int
 double_to_int_precise(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	double d = mem->u.r;
 	if (d < 0 && d >= (double)INT64_MIN && (double)(int64_t)d == d) {
 		mem->u.i = (int64_t)d;
-		mem->flags = MEM_Int;
+		mem->type = MEM_TYPE_INT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_INTEGER;
 		return 0;
 	}
 	if (d >= 0 && d < (double)UINT64_MAX && (double)(uint64_t)d == d) {
 		mem->u.u = (uint64_t)d;
-		mem->flags = MEM_UInt;
+		mem->type = MEM_TYPE_UINT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_UNSIGNED;
 		return 0;
 	}
@@ -682,10 +721,12 @@ double_to_int_precise(struct Mem *mem)
 static inline int
 double_to_uint(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	double d = mem->u.r;
 	if (d >= 0 && d < (double)UINT64_MAX) {
 		mem->u.u = (uint64_t)d;
-		mem->flags = MEM_UInt;
+		mem->type = MEM_TYPE_UINT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_UNSIGNED;
 		return 0;
 	}
@@ -695,10 +736,12 @@ double_to_uint(struct Mem *mem)
 static inline int
 double_to_uint_precise(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	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->type = MEM_TYPE_UINT;
+		assert(mem->flags == 0);
 		mem->field_type = FIELD_TYPE_UNSIGNED;
 		return 0;
 	}
@@ -708,11 +751,13 @@ double_to_uint_precise(struct Mem *mem)
 static inline int
 double_to_str0(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	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->type = MEM_TYPE_STR;
+	mem->flags = MEM_Term;
 	mem->field_type = FIELD_TYPE_STRING;
 	return 0;
 }
@@ -720,8 +765,10 @@ double_to_str0(struct Mem *mem)
 static inline int
 double_to_bool(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_DOUBLE);
 	mem->u.b = mem->u.r != 0.;
-	mem->flags = MEM_Bool;
+	mem->type = MEM_TYPE_BOOL;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_BOOLEAN;
 	return 0;
 }
@@ -729,8 +776,10 @@ double_to_bool(struct Mem *mem)
 static inline int
 bool_to_int(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_BOOL);
 	mem->u.u = (uint64_t)mem->u.b;
-	mem->flags = MEM_UInt;
+	mem->type = MEM_TYPE_UINT;
+	assert(mem->flags == 0);
 	mem->field_type = FIELD_TYPE_UNSIGNED;
 	return 0;
 }
@@ -738,6 +787,7 @@ bool_to_int(struct Mem *mem)
 static inline int
 bool_to_str0(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_BOOL);
 	const char *str = mem->u.b ? "TRUE" : "FALSE";
 	return mem_copy_str0(mem, str);
 }
@@ -745,6 +795,7 @@ bool_to_str0(struct Mem *mem)
 static inline int
 array_to_str0(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_ARRAY);
 	const char *str = mp_str(mem->z);
 	return mem_copy_str0(mem, str);
 }
@@ -752,6 +803,7 @@ array_to_str0(struct Mem *mem)
 static inline int
 map_to_str0(struct Mem *mem)
 {
+	assert(mem->type == MEM_TYPE_MAP);
 	const char *str = mp_str(mem->z);
 	return mem_copy_str0(mem, str);
 }
@@ -759,14 +811,14 @@ map_to_str0(struct Mem *mem)
 int
 mem_to_int(struct Mem *mem)
 {
-	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
-	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+	assert(mem->type < MEM_TYPE_INVALID);
+	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 		return 0;
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
 		return bytes_to_int(mem);
-	if ((mem->flags & MEM_Real) != 0)
+	if (mem->type == MEM_TYPE_DOUBLE)
 		return double_to_int(mem);
-	if ((mem->flags & MEM_Bool) != 0)
+	if (mem->type == MEM_TYPE_BOOL)
 		return bool_to_int(mem);
 	return -1;
 }
@@ -774,12 +826,12 @@ mem_to_int(struct Mem *mem)
 int
 mem_to_int_precise(struct Mem *mem)
 {
-	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
-	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+	assert(mem->type < MEM_TYPE_INVALID);
+	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 		return 0;
-	if ((mem->flags & MEM_Str) != 0)
+	if (mem->type == MEM_TYPE_STR)
 		return bytes_to_int(mem);
-	if ((mem->flags & MEM_Real) != 0)
+	if (mem->type == MEM_TYPE_DOUBLE)
 		return double_to_int_precise(mem);
 	return -1;
 }
@@ -787,12 +839,12 @@ mem_to_int_precise(struct Mem *mem)
 int
 mem_to_double(struct Mem *mem)
 {
-	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
-	if ((mem->flags & MEM_Real) != 0)
+	assert(mem->type < MEM_TYPE_INVALID);
+	if (mem->type == MEM_TYPE_DOUBLE)
 		return 0;
-	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 		return int_to_double(mem);
-	if ((mem->flags & MEM_Str) != 0)
+	if (mem->type == MEM_TYPE_STR)
 		return bytes_to_double(mem);
 	return -1;
 }
@@ -800,12 +852,12 @@ 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)
+	assert(mem->type < MEM_TYPE_INVALID);
+	if (mem_is_num(mem))
 		return 0;
-	if ((mem->flags & MEM_Bool) != 0)
+	if (mem->type == MEM_TYPE_BOOL)
 		return bool_to_int(mem);
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
+	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) {
 		if (bytes_to_int(mem) == 0)
 			return 0;
 		return bytes_to_double(mem);
@@ -816,72 +868,77 @@ mem_to_number(struct Mem *mem)
 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)
+	assert(mem->type < MEM_TYPE_INVALID);
+	switch (mem->type) {
+	case MEM_TYPE_STR:
+		if ((mem->flags & MEM_Term) != 0)
+			return 0;
 		return str_to_str0(mem);
-	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+	case MEM_TYPE_INT:
+	case MEM_TYPE_UINT:
 		return int_to_str0(mem);
-	if ((mem->flags & MEM_Real) != 0)
+	case MEM_TYPE_DOUBLE:
 		return double_to_str0(mem);
-	if ((mem->flags & MEM_Bool) != 0)
+	case MEM_TYPE_BOOL:
 		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);
+	case MEM_TYPE_BIN:
+		return bin_to_str0(mem);
+	case MEM_TYPE_MAP:
+		return map_to_str0(mem);
+	case MEM_TYPE_ARRAY:
 		return array_to_str0(mem);
+	default:
+		return -1;
 	}
-	return -1;
 }
 
 int
 mem_to_str(struct Mem *mem)
 {
-	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
-	if ((mem->flags & MEM_Str) != 0)
+	assert(mem->type < MEM_TYPE_INVALID);
+	switch (mem->type) {
+	case MEM_TYPE_STR:
 		return 0;
-	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+	case MEM_TYPE_INT:
+	case MEM_TYPE_UINT:
 		return int_to_str0(mem);
-	if ((mem->flags & MEM_Real) != 0)
+	case MEM_TYPE_DOUBLE:
 		return double_to_str0(mem);
-	if ((mem->flags & MEM_Bool) != 0)
+	case MEM_TYPE_BOOL:
 		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);
+	case MEM_TYPE_BIN:
+		return bin_to_str(mem);
+	case MEM_TYPE_MAP:
+		return map_to_str0(mem);
+	case MEM_TYPE_ARRAY:
 		return array_to_str0(mem);
+	default:
+		return -1;
 	}
-	return -1;
 }
 
 int
 mem_cast_explicit(struct Mem *mem, enum field_type type)
 {
-	if ((mem->flags & MEM_Null) != 0) {
+	if (mem->type == MEM_TYPE_NULL) {
 		mem->field_type = type;
 		return 0;
 	}
 	switch (type) {
 	case FIELD_TYPE_UNSIGNED:
-		if ((mem->flags & MEM_UInt) != 0)
+		switch (mem->type) {
+		case MEM_TYPE_UINT:
 			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)
+		case MEM_TYPE_STR:
+		case MEM_TYPE_BIN:
 			return bytes_to_uint(mem);
-		if ((mem->flags & MEM_Real) != 0)
+		case MEM_TYPE_DOUBLE:
 			return double_to_int(mem);
-		if ((mem->flags & MEM_Bool) != 0)
+		case MEM_TYPE_BOOL:
 			return bool_to_int(mem);
-		return -1;
+		default:
+			return -1;
+		}
 	case FIELD_TYPE_STRING:
 		return mem_to_str(mem);
 	case FIELD_TYPE_DOUBLE:
@@ -889,26 +946,29 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
 	case FIELD_TYPE_INTEGER:
 		return mem_to_int(mem);
 	case FIELD_TYPE_BOOLEAN:
-		if ((mem->flags & MEM_Bool) != 0)
+		switch (mem->type) {
+		case MEM_TYPE_BOOL:
 			return 0;
-		if ((mem->flags & (MEM_UInt | MEM_Int)) != 0)
+		case MEM_TYPE_INT:
+		case MEM_TYPE_UINT:
 			return int_to_bool(mem);
-		if ((mem->flags & MEM_Str) != 0)
+		case MEM_TYPE_STR:
 			return str_to_bool(mem);
-		if ((mem->flags & MEM_Real) != 0)
+		case MEM_TYPE_DOUBLE:
 			return double_to_bool(mem);
-		return -1;
+		default:
+			return -1;
+		}
 	case FIELD_TYPE_VARBINARY:
-		if ((mem->flags & MEM_Blob) != 0)
-			return 0;
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return str_to_bin(mem);
+		if (mem_is_bytes(mem))
+			return 0;
 		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)
+		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
 			return -1;
 		return 0;
 	default:
@@ -920,56 +980,56 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
 int
 mem_cast_implicit(struct Mem *mem, enum field_type type)
 {
-	if ((mem->flags & MEM_Null) != 0) {
+	if (mem->type == MEM_TYPE_NULL) {
 		mem->field_type = type;
 		return 0;
 	}
 	switch (type) {
 	case FIELD_TYPE_UNSIGNED:
-		if ((mem->flags & MEM_UInt) != 0)
+		if (mem->type == MEM_TYPE_UINT)
 			return 0;
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_uint(mem);
 		return -1;
 	case FIELD_TYPE_STRING:
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return 0;
 		return -1;
 	case FIELD_TYPE_DOUBLE:
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return 0;
-		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 			return int_to_double(mem);
 		return -1;
 	case FIELD_TYPE_INTEGER:
-		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 			return 0;
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_int(mem);
 		return -1;
 	case FIELD_TYPE_BOOLEAN:
-		if ((mem->flags & MEM_Bool) != 0)
+		if (mem->type == MEM_TYPE_BOOL)
 			return 0;
 		return -1;
 	case FIELD_TYPE_VARBINARY:
-		if ((mem->flags & MEM_Blob) != 0)
+		if ((mem->type & (MEM_TYPE_BIN | MEM_TYPE_MAP |
+				  MEM_TYPE_ARRAY)) != 0)
 			return 0;
 		return -1;
 	case FIELD_TYPE_NUMBER:
-		if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
+		if (mem_is_num(mem))
 			return 0;
 		return -1;
 	case FIELD_TYPE_MAP:
-		if (mem_is_map(mem))
+		if (mem->type == MEM_TYPE_MAP)
 			return 0;
 		return -1;
 	case FIELD_TYPE_ARRAY:
-		if (mem_is_array(mem))
+		if (mem->type == MEM_TYPE_ARRAY)
 			return 0;
 		return -1;
 	case FIELD_TYPE_SCALAR:
-		if ((mem->flags & MEM_Blob) != 0 &&
-		    (mem->flags & MEM_Subtype) != 0)
+		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
 			return -1;
 		return 0;
 	case FIELD_TYPE_ANY:
@@ -983,66 +1043,65 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
 int
 mem_cast_implicit_old(struct Mem *mem, enum field_type type)
 {
-	if (mem_is_null(mem))
+	if (mem->type == MEM_TYPE_NULL)
 		return 0;
 	switch (type) {
 	case FIELD_TYPE_UNSIGNED:
-		if ((mem->flags & MEM_UInt) != 0)
+		if (mem->type == MEM_TYPE_UINT)
 			return 0;
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_uint_precise(mem);
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return bytes_to_uint(mem);
 		return -1;
 	case FIELD_TYPE_STRING:
-		if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+		if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
 			return 0;
-		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 			return int_to_str0(mem);
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_str0(mem);
 		return -1;
 	case FIELD_TYPE_DOUBLE:
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return 0;
-		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 			return int_to_double(mem);
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return bin_to_str(mem);
 		return -1;
 	case FIELD_TYPE_INTEGER:
-		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
+		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
 			return 0;
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return bytes_to_int(mem);
-		if ((mem->flags & MEM_Real) != 0)
+		if (mem->type == MEM_TYPE_DOUBLE)
 			return double_to_int_precise(mem);
 		return -1;
 	case FIELD_TYPE_BOOLEAN:
-		if ((mem->flags & MEM_Bool) != 0)
+		if (mem->type == MEM_TYPE_BOOL)
 			return 0;
 		return -1;
 	case FIELD_TYPE_VARBINARY:
-		if ((mem->flags & MEM_Blob) != 0)
+		if (mem->type == MEM_TYPE_BIN)
 			return 0;
 		return -1;
 	case FIELD_TYPE_NUMBER:
-		if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
+		if (mem_is_num(mem))
 			return 0;
-		if ((mem->flags & MEM_Str) != 0)
+		if (mem->type == MEM_TYPE_STR)
 			return mem_to_number(mem);
 		return -1;
 	case FIELD_TYPE_MAP:
-		if (mem_is_map(mem))
+		if (mem->type == MEM_TYPE_MAP)
 			return 0;
 		return -1;
 	case FIELD_TYPE_ARRAY:
-		if (mem_is_array(mem))
+		if (mem->type == MEM_TYPE_ARRAY)
 			return 0;
 		return -1;
 	case FIELD_TYPE_SCALAR:
-		if ((mem->flags & MEM_Blob) != 0 &&
-		    (mem->flags & MEM_Subtype) != 0)
+		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
 			return -1;
 		return 0;
 	default:
@@ -1054,19 +1113,19 @@ 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)
 {
-	if ((mem->flags & MEM_Int) != 0) {
+	if (mem->type == MEM_TYPE_INT) {
 		*i = mem->u.i;
 		*is_neg = true;
 		return 0;
 	}
-	if ((mem->flags & MEM_UInt) != 0) {
+	if (mem->type == MEM_TYPE_UINT) {
 		*i = mem->u.i;
 		*is_neg = false;
 		return 0;
 	}
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
+	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
 		return sql_atoi64(mem->z, i, is_neg, mem->n);
-	if ((mem->flags & MEM_Real) != 0) {
+	if (mem->type == MEM_TYPE_DOUBLE) {
 		double d = mem->u.r;
 		if (d < 0 && d >= (double)INT64_MIN) {
 			*i = (int64_t)d;
@@ -1086,20 +1145,20 @@ mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
 int
 mem_get_uint(const struct Mem *mem, uint64_t *u)
 {
-	if ((mem->flags & MEM_Int) != 0)
+	if (mem->type == MEM_TYPE_INT)
 		return -1;
-	if ((mem->flags & MEM_UInt) != 0) {
+	if (mem->type == MEM_TYPE_UINT) {
 		*u = mem->u.u;
 		return 0;
 	}
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
+	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 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) {
+	if (mem->type == MEM_TYPE_DOUBLE) {
 		double d = mem->u.r;
 		if (d >= 0 && d < (double)UINT64_MAX) {
 			*u = (uint64_t)d;
@@ -1113,19 +1172,19 @@ mem_get_uint(const struct Mem *mem, uint64_t *u)
 int
 mem_get_double(const struct Mem *mem, double *d)
 {
-	if ((mem->flags & MEM_Real) != 0) {
+	if (mem->type == MEM_TYPE_DOUBLE) {
 		*d = mem->u.r;
 		return 0;
 	}
-	if ((mem->flags & MEM_Int) != 0) {
+	if (mem->type == MEM_TYPE_INT) {
 		*d = (double)mem->u.i;
 		return 0;
 	}
-	if ((mem->flags & MEM_UInt) != 0) {
+	if (mem->type == MEM_TYPE_UINT) {
 		*d = (double)mem->u.u;
 		return 0;
 	}
-	if ((mem->flags & MEM_Str) != 0) {
+	if (mem->type == MEM_TYPE_STR) {
 		if (sqlAtoF(mem->z, d, mem->n) == 0)
 			return -1;
 		return 0;
@@ -1136,7 +1195,7 @@ mem_get_double(const struct Mem *mem, double *d)
 int
 mem_get_bool(const struct Mem *mem, bool *b)
 {
-	if ((mem->flags & MEM_Bool) != 0) {
+	if (mem->type == MEM_TYPE_BOOL) {
 		*b = mem->u.b;
 		return 0;
 	}
@@ -1146,7 +1205,7 @@ mem_get_bool(const struct Mem *mem, bool *b)
 int
 mem_get_str0(const struct Mem *mem, const char **s)
 {
-	if ((mem->flags & MEM_Str) == 0 || (mem->flags & MEM_Term) == 0)
+	if (mem->type != MEM_TYPE_STR || (mem->flags & MEM_Term) == 0)
 		return -1;
 	*s = mem->z;
 	return 0;
@@ -1155,11 +1214,11 @@ mem_get_str0(const struct Mem *mem, const char **s)
 int
 mem_get_bin(const struct Mem *mem, const char **s)
 {
-	if ((mem->flags & MEM_Str) != 0) {
+	if (mem->type == MEM_TYPE_STR) {
 		*s = mem->n > 0 ? mem->z : NULL;
 		return 0;
 	}
-	if ((mem->flags & MEM_Blob) == 0 || (mem->flags & MEM_Zero) != 0)
+	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
 		return -1;
 	*s = mem->z;
 	return 0;
@@ -1168,9 +1227,9 @@ mem_get_bin(const struct Mem *mem, const char **s)
 int
 mem_len(const struct Mem *mem, uint32_t *len)
 {
-	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
+	if (!mem_is_bytes(mem))
 		return -1;
-	if ((mem->flags & MEM_Blob) !=0 && (mem->flags & MEM_Zero) != 0)
+	if (mem->type == MEM_TYPE_BIN && (mem->flags & MEM_Zero) != 0)
 		*len = mem->n + mem->u.nZero;
 	else
 		*len = mem->n;
@@ -1180,7 +1239,7 @@ mem_len(const struct Mem *mem, uint32_t *len)
 int
 mem_get_agg(const struct Mem *mem, void **accum)
 {
-	if ((mem->flags & MEM_Agg) == 0)
+	if (mem->type != MEM_TYPE_AGG)
 		return -1;
 	*accum = mem->z;
 	return 0;
@@ -1191,16 +1250,17 @@ mem_copy(struct Mem *to, const struct Mem *from)
 {
 	mem_clear(to);
 	to->u = from->u;
+	to->type = from->type;
 	to->flags = from->flags;
-	to->subtype = from->subtype;
 	to->field_type = from->field_type;
 	to->n = from->n;
 	to->z = from->z;
-	if ((to->flags & (MEM_Str | MEM_Blob)) == 0)
+	if (!mem_is_bytes(to))
 		return 0;
 	if ((to->flags & MEM_Static) != 0)
 		return 0;
-	if ((to->flags & (MEM_Zero | MEM_Blob)) == (MEM_Zero | MEM_Blob))
+	assert((to->flags & MEM_Zero) == 0 || to->type == MEM_TYPE_BIN);
+	if ((to->flags & MEM_Zero) != 0)
 		return sqlVdbeMemExpandBlob(to);
 	to->zMalloc = sqlDbReallocOrFree(to->db, to->zMalloc, to->n);
 	if (to->zMalloc == NULL)
@@ -1208,7 +1268,7 @@ mem_copy(struct Mem *to, const struct Mem *from)
 	to->szMalloc = sqlDbMallocSize(to->db, to->zMalloc);
 	memcpy(to->zMalloc, to->z, to->n);
 	to->z = to->zMalloc;
-	to->flags &= (MEM_Str | MEM_Blob | MEM_Term | MEM_Subtype);
+	to->flags &= MEM_Term;
 	return 0;
 }
 
@@ -1217,16 +1277,16 @@ mem_copy_as_ephemeral(struct Mem *to, const struct Mem *from)
 {
 	mem_clear(to);
 	to->u = from->u;
+	to->type = from->type;
 	to->flags = from->flags;
-	to->subtype = from->subtype;
 	to->field_type = from->field_type;
 	to->n = from->n;
 	to->z = from->z;
-	if ((to->flags & (MEM_Str | MEM_Blob)) == 0)
+	if (!mem_is_bytes(to))
 		return;
 	if ((to->flags & (MEM_Static | MEM_Ephem)) != 0)
 		return;
-	to->flags &= (MEM_Str | MEM_Blob | MEM_Term | MEM_Zero | MEM_Subtype);
+	to->flags &= MEM_Term | MEM_Zero;
 	to->flags |= MEM_Ephem;
 	return;
 }
@@ -1236,7 +1296,8 @@ mem_move(struct Mem *to, struct Mem *from)
 {
 	mem_destroy(to);
 	memcpy(to, from, sizeof(*to));
-	from->flags = MEM_Null;
+	from->type = MEM_TYPE_NULL;
+	from->flags = 0;
 	from->szMalloc = 0;
 	from->zMalloc = NULL;
 }
@@ -1247,7 +1308,7 @@ try_return_null(const struct Mem *a, const struct Mem *b, struct Mem *result,
 {
 	mem_clear(result);
 	result->field_type = type;
-	return (((a->flags | b->flags) & MEM_Null) != 0);
+	return ((a->type | b->type) & MEM_TYPE_NULL) != 0;
 }
 
 int
@@ -1258,7 +1319,7 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 		if (try_return_null(a, b, result, FIELD_TYPE_STRING))
 			return 0;
 	} else {
-		if (((a->flags | b->flags) & MEM_Null) != 0) {
+		if (((a->type | b->type) & MEM_TYPE_NULL) != 0) {
 			mem_clear(a);
 			result->field_type = FIELD_TYPE_STRING;
 			return 0;
@@ -1266,19 +1327,19 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 	}
 
 	/* Concatenation operation can be applied only to strings and blobs. */
-	if ((b->flags & (MEM_Str | MEM_Blob)) == 0) {
+	if (((b->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)) {
 		diag_set(ClientError, ER_INCONSISTENT_TYPES,
 			 "text or varbinary", mem_type_to_str(b));
 		return -1;
 	}
-	if ((a->flags & (MEM_Str | MEM_Blob)) == 0) {
+	if (((a->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)) {
 		diag_set(ClientError, ER_INCONSISTENT_TYPES,
 			 "text or varbinary", mem_type_to_str(a));
 		return -1;
 	}
 
 	/* Moreover, both operands must be of the same type. */
-	if ((b->flags & MEM_Str) != (a->flags & MEM_Str)) {
+	if (b->type != a->type) {
 		diag_set(ClientError, ER_INCONSISTENT_TYPES,
 			 mem_type_to_str(a), mem_type_to_str(b));
 		return -1;
@@ -1295,8 +1356,9 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 	if (sqlVdbeMemGrow(result, size, result == a) != 0)
 		return -1;
 
-	result->flags = a->flags & (MEM_Str | MEM_Blob);
-	if ((result->flags & MEM_Blob) != 0)
+	result->type = a->type == MEM_TYPE_STR ? MEM_TYPE_STR : MEM_TYPE_BIN;
+	result->flags = 0;
+	if (result->type == MEM_TYPE_BIN)
 		result->field_type = FIELD_TYPE_VARBINARY;
 	if (result != a)
 		memcpy(result->z, a->z, a->n);
@@ -1311,36 +1373,34 @@ struct sql_num {
 		uint64_t u;
 		double d;
 	};
-	int type;
+	enum mem_type type;
 	bool is_neg;
 };
 
 static int
 get_number(const struct Mem *mem, struct sql_num *number)
 {
-	if ((mem->flags & MEM_Real) != 0) {
+	if (mem->type == MEM_TYPE_DOUBLE) {
 		number->d = mem->u.r;
-		number->type = MEM_Real;
+		number->type = MEM_TYPE_DOUBLE;
 		return 0;
 	}
-	if ((mem->flags & MEM_Int) != 0) {
+	if (mem->type == MEM_TYPE_INT) {
 		number->i = mem->u.i;
-		number->type = MEM_Int;
+		number->type = MEM_TYPE_INT;
 		number->is_neg = true;
 		return 0;
 	}
-	if ((mem->flags & MEM_UInt) != 0) {
+	if (mem->type == MEM_TYPE_UINT) {
 		number->u = mem->u.u;
-		number->type = MEM_UInt;
+		number->type = MEM_TYPE_UINT;
 		number->is_neg = false;
 		return 0;
 	}
-	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
-		return -1;
-	if ((mem->flags & MEM_Subtype) != 0)
+	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)
 		return -1;
 	if (sql_atoi64(mem->z, &number->i, &number->is_neg, mem->n) == 0) {
-		number->type = number->is_neg ? MEM_Int : MEM_UInt;
+		number->type = number->is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
 		/*
 		 * The next line should be removed along with the is_neg field
 		 * of struct sql_num. The integer type tells us about the sign.
@@ -1351,7 +1411,7 @@ get_number(const struct Mem *mem, struct sql_num *number)
 		return 0;
 	}
 	if (sqlAtoF(mem->z, &number->d, mem->n) != 0) {
-		number->type = MEM_Real;
+		number->type = MEM_TYPE_DOUBLE;
 		return 0;
 	}
 	return -1;
@@ -1372,16 +1432,16 @@ arithmetic_prepare(const struct Mem *left, const struct Mem *right,
 		return -1;
 	}
 	assert(a->type != 0 && b->type != 0);
-	if (a->type == MEM_Real && b->type != MEM_Real) {
-		b->d = b->type == MEM_Int ? (double)b->i : (double)b->u;
-		b->type = MEM_Real;
+	if (a->type == b->type || ((a->type | b->type) & MEM_TYPE_DOUBLE) == 0)
 		return 0;
-	}
-	if (a->type != MEM_Real && b->type == MEM_Real) {
-		a->d = a->type == MEM_Int ? (double)a->i : (double)a->u;
-		a->type = MEM_Real;
+	if (a->type == MEM_TYPE_DOUBLE) {
+		b->d = b->type == MEM_TYPE_INT ? (double)b->i : (double)b->u;
+		b->type = MEM_TYPE_DOUBLE;
 		return 0;
 	}
+	assert(b->type == MEM_TYPE_DOUBLE);
+	a->d = a->type == MEM_TYPE_INT ? (double)a->i : (double)a->u;
+	a->type = MEM_TYPE_DOUBLE;
 	return 0;
 }
 
@@ -1395,10 +1455,11 @@ mem_add(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (arithmetic_prepare(left, right, &a, &b) != 0)
 		return -1;
 
-	assert(a.type != MEM_Real || a.type == b.type);
-	if (a.type == MEM_Real) {
+	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
+	if (a.type == MEM_TYPE_DOUBLE) {
 		result->u.r = a.d + b.d;
-		result->flags = MEM_Real;
+		result->type = MEM_TYPE_DOUBLE;
+		assert(result->flags == 0);
 		return 0;
 	}
 
@@ -1409,7 +1470,8 @@ mem_add(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		return -1;
 	}
 	result->u.i = res;
-	result->flags = is_neg ? MEM_Int : MEM_UInt;
+	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1423,10 +1485,11 @@ mem_sub(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (arithmetic_prepare(left, right, &a, &b) != 0)
 		return -1;
 
-	assert(a.type != MEM_Real || a.type == b.type);
-	if (a.type == MEM_Real) {
+	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
+	if (a.type == MEM_TYPE_DOUBLE) {
 		result->u.r = a.d - b.d;
-		result->flags = MEM_Real;
+		result->type = MEM_TYPE_DOUBLE;
+		assert(result->flags == 0);
 		return 0;
 	}
 
@@ -1437,7 +1500,8 @@ mem_sub(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		return -1;
 	}
 	result->u.i = res;
-	result->flags = is_neg ? MEM_Int : MEM_UInt;
+	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1451,10 +1515,11 @@ mem_mul(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (arithmetic_prepare(left, right, &a, &b) != 0)
 		return -1;
 
-	assert(a.type != MEM_Real || a.type == b.type);
-	if (a.type == MEM_Real) {
+	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
+	if (a.type == MEM_TYPE_DOUBLE) {
 		result->u.r = a.d * b.d;
-		result->flags = MEM_Real;
+		result->type = MEM_TYPE_DOUBLE;
+		assert(result->flags == 0);
 		return 0;
 	}
 
@@ -1465,7 +1530,8 @@ mem_mul(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		return -1;
 	}
 	result->u.i = res;
-	result->flags = is_neg ? MEM_Int : MEM_UInt;
+	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1479,15 +1545,16 @@ mem_div(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (arithmetic_prepare(left, right, &a, &b) != 0)
 		return -1;
 
-	assert(a.type != MEM_Real || a.type == b.type);
-	if (a.type == MEM_Real) {
+	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
+	if (a.type == MEM_TYPE_DOUBLE) {
 		if (b.d == 0.) {
 			diag_set(ClientError, ER_SQL_EXECUTE,
 				 "division by zero");
 			return -1;
 		}
 		result->u.r = a.d / b.d;
-		result->flags = MEM_Real;
+		result->type = MEM_TYPE_DOUBLE;
+		assert(result->flags == 0);
 		return 0;
 	}
 
@@ -1502,7 +1569,8 @@ mem_div(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		return -1;
 	}
 	result->u.i = res;
-	result->flags = is_neg ? MEM_Int : MEM_UInt;
+	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1516,14 +1584,14 @@ mem_rem(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (arithmetic_prepare(left, right, &a, &b) != 0)
 		return -1;
 
-	assert(a.type != MEM_Real || a.type == b.type);
+	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
 	/*
 	 * TODO: This operation works wrong when double d > INT64_MAX and
 	 * d < UINT64_MAX. Also, there may be precision losses due to
 	 * conversion integer to double and back.
 	 */
-	a.i = a.type == MEM_Real ? (int64_t)a.d : a.i;
-	b.i = b.type == MEM_Real ? (int64_t)b.d : b.i;
+	a.i = a.type == MEM_TYPE_DOUBLE ? (int64_t)a.d : a.i;
+	b.i = b.type == MEM_TYPE_DOUBLE ? (int64_t)b.d : b.i;
 	if (b.i == 0) {
 		diag_set(ClientError, ER_SQL_EXECUTE, "division by zero");
 		return -1;
@@ -1535,7 +1603,8 @@ mem_rem(const struct Mem *left, const struct Mem *right, struct Mem *result)
 		return -1;
 	}
 	result->u.i = res;
-	result->flags = is_neg ? MEM_Int : MEM_UInt;
+	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1567,7 +1636,8 @@ mem_bit_and(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (bitwise_prepare(left, right, &a, &b) != 0)
 		return -1;
 	result->u.i = a & b;
-	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
+	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1581,7 +1651,8 @@ mem_bit_or(const struct Mem *left, const struct Mem *right, struct Mem *result)
 	if (bitwise_prepare(left, right, &a, &b) != 0)
 		return -1;
 	result->u.i = a | b;
-	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
+	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1603,7 +1674,8 @@ mem_shift_left(const struct Mem *left, const struct Mem *right,
 		result->u.i = 0;
 	else
 		result->u.i = a << b;
-	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
+	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1625,7 +1697,8 @@ mem_shift_right(const struct Mem *left, const struct Mem *right,
 		result->u.i = a >= 0 ? 0 : -1;
 	else
 		result->u.i = a >> b;
-	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
+	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
@@ -1634,7 +1707,7 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
 {
 	mem_clear(result);
 	result->field_type = FIELD_TYPE_INTEGER;
-	if ((mem->flags & MEM_Null) != 0)
+	if (mem->type == MEM_TYPE_NULL)
 		return 0;
 	int64_t i;
 	bool unused;
@@ -1644,14 +1717,15 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
 		return -1;
 	}
 	result->u.i = ~i;
-	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
+	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
+	assert(result->flags == 0);
 	return 0;
 }
 
 int
 mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
 {
-	if ((a->flags & b->flags & MEM_Bool) == 0)
+	if (a->type != MEM_TYPE_BOOL || b->type != MEM_TYPE_BOOL)
 		return -1;
 	if (a->u.b == b->u.b)
 		*result = 0;
@@ -1665,7 +1739,7 @@ mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
 int
 mem_cmp_bin(const struct Mem *a, const struct Mem *b, int *result)
 {
-	if ((a->flags & b->flags & MEM_Blob) == 0)
+	if ((a->type & b->type & MEM_TYPE_BIN) == 0)
 		return -1;
 	int an = a->n;
 	int bn = b->n;
@@ -1722,8 +1796,8 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
 	}
 	if (get_number(left, &a) != 0)
 		return -1;
-	if (a.type == MEM_Real) {
-		if (b.type == MEM_Real) {
+	if (a.type == MEM_TYPE_DOUBLE) {
+		if (b.type == MEM_TYPE_DOUBLE) {
 			if (a.d > b.d)
 				*result = 1;
 			else if (a.d < b.d)
@@ -1732,14 +1806,14 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
 				*result = 0;
 			return 0;
 		}
-		if (b.type == MEM_Int)
+		if (b.type == MEM_TYPE_INT)
 			*result = double_compare_nint64(a.d, b.i, 1);
 		else
 			*result = double_compare_uint64(a.d, b.u, 1);
 		return 0;
 	}
-	if (a.type == MEM_Int) {
-		if (b.type == MEM_Int) {
+	if (a.type == MEM_TYPE_INT) {
+		if (b.type == MEM_TYPE_INT) {
 			if (a.i > b.i)
 				*result = 1;
 			else if (a.i < b.i)
@@ -1748,14 +1822,14 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
 				*result = 0;
 			return 0;
 		}
-		if (b.type == MEM_UInt)
+		if (b.type == MEM_TYPE_UINT)
 			*result = -1;
 		else
 			*result = double_compare_nint64(b.d, a.i, -1);
 		return 0;
 	}
-	assert(a.type == MEM_UInt);
-	if (b.type == MEM_UInt) {
+	assert(a.type == MEM_TYPE_UINT);
+	if (b.type == MEM_TYPE_UINT) {
 		if (a.u > b.u)
 			*result = 1;
 		else if (a.u < b.u)
@@ -1764,7 +1838,7 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
 			*result = 0;
 		return 0;
 	}
-	if (b.type == MEM_Int)
+	if (b.type == MEM_TYPE_INT)
 		*result = 1;
 	else
 		*result = double_compare_uint64(b.d, a.u, -1);
@@ -1778,15 +1852,15 @@ mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
 	char *a;
 	uint32_t an;
 	char bufl[BUF_SIZE];
-	if ((left->flags & MEM_Str) != 0) {
+	if (left->type == MEM_TYPE_STR) {
 		a = left->z;
 		an = left->n;
 	} else {
-		assert((left->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0);
+		assert(mem_is_num(left));
 		a = &bufl[0];
-		if ((left->flags & MEM_Int) != 0)
+		if (left->type == MEM_TYPE_INT)
 			sql_snprintf(BUF_SIZE, a, "%lld", left->u.i);
-		else if ((left->flags & MEM_UInt) != 0)
+		else if (left->type == MEM_TYPE_UINT)
 			sql_snprintf(BUF_SIZE, a, "%llu", left->u.u);
 		else
 			sql_snprintf(BUF_SIZE, a, "%!.15g", left->u.r);
@@ -1796,15 +1870,15 @@ mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
 	char *b;
 	uint32_t bn;
 	char bufr[BUF_SIZE];
-	if ((right->flags & MEM_Str) != 0) {
+	if (right->type == MEM_TYPE_STR) {
 		b = right->z;
 		bn = right->n;
 	} else {
-		assert((right->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0);
+		assert(mem_is_num(right));
 		b = &bufr[0];
-		if ((right->flags & MEM_Int) != 0)
+		if (right->type == MEM_TYPE_INT)
 			sql_snprintf(BUF_SIZE, b, "%lld", right->u.i);
-		else if ((right->flags & MEM_UInt) != 0)
+		else if (right->type == MEM_TYPE_UINT)
 			sql_snprintf(BUF_SIZE, b, "%llu", right->u.u);
 		else
 			sql_snprintf(BUF_SIZE, b, "%!.15g", right->u.r);
@@ -1822,13 +1896,6 @@ mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
 	return 0;
 }
 
-static inline bool
-mem_has_msgpack_subtype(struct Mem *mem)
-{
-	return (mem->flags & MEM_Subtype) != 0 &&
-	       mem->subtype == SQL_SUBTYPE_MSGPACK;
-}
-
 /*
  * Both *pMem1 and *pMem2 contain string values. Compare the two values
  * using the collation sequence pColl. As usual, return a negative , zero
@@ -1864,20 +1931,22 @@ char *
 mem_type_to_str(const struct Mem *p)
 {
 	assert(p != NULL);
-	switch (p->flags & MEM_PURE_TYPE_MASK) {
-	case MEM_Null:
+	switch (p->type) {
+	case MEM_TYPE_NULL:
 		return "NULL";
-	case MEM_Str:
+	case MEM_TYPE_STR:
 		return "text";
-	case MEM_Int:
+	case MEM_TYPE_INT:
 		return "integer";
-	case MEM_UInt:
+	case MEM_TYPE_UINT:
 		return "unsigned";
-	case MEM_Real:
+	case MEM_TYPE_DOUBLE:
 		return "real";
-	case MEM_Blob:
+	case MEM_TYPE_ARRAY:
+	case MEM_TYPE_MAP:
+	case MEM_TYPE_BIN:
 		return "varbinary";
-	case MEM_Bool:
+	case MEM_TYPE_BOOL:
 		return "boolean";
 	default:
 		unreachable();
@@ -1887,28 +1956,32 @@ mem_type_to_str(const struct Mem *p)
 enum mp_type
 mem_mp_type(struct Mem *mem)
 {
-	switch (mem->flags & MEM_PURE_TYPE_MASK) {
-	case MEM_Int:
-		return MP_INT;
-	case MEM_UInt:
+	assert(mem->type < MEM_TYPE_INVALID);
+	switch (mem->type) {
+	case MEM_TYPE_NULL:
+		return MP_NIL;
+	case MEM_TYPE_UINT:
 		return MP_UINT;
-	case MEM_Real:
-		return MP_DOUBLE;
-	case MEM_Str:
+	case MEM_TYPE_INT:
+		return MP_INT;
+	case MEM_TYPE_STR:
 		return MP_STR;
-	case MEM_Blob:
-		if ((mem->flags & MEM_Subtype) == 0 ||
-		     mem->subtype != SQL_SUBTYPE_MSGPACK)
-			return MP_BIN;
-		assert(mp_typeof(*mem->z) == MP_MAP ||
-		       mp_typeof(*mem->z) == MP_ARRAY);
-		return mp_typeof(*mem->z);
-	case MEM_Bool:
+	case MEM_TYPE_BIN:
+		return MP_BIN;
+	case MEM_TYPE_ARRAY:
+		return MP_ARRAY;
+	case MEM_TYPE_MAP:
+		return MP_MAP;
+	case MEM_TYPE_BOOL:
 		return MP_BOOL;
-	case MEM_Null:
-		return MP_NIL;
-	default: unreachable();
+	case MEM_TYPE_FLOAT:
+		return MP_FLOAT;
+	case MEM_TYPE_DOUBLE:
+		return MP_DOUBLE;
+	default:
+		unreachable();
 	}
+	return MP_NIL;
 }
 
 /* EVIDENCE-OF: R-12793-43283 Every value in sql has one of five
@@ -1944,11 +2017,6 @@ sqlVdbeCheckMemInvariants(Mem * p)
 	 */
 	assert((p->flags & MEM_Dyn) == 0 || p->szMalloc == 0);
 
-	/* Cannot be both MEM_Int and MEM_Real at the same time */
-	assert((p->flags & (MEM_Int | MEM_Real)) != (MEM_Int | MEM_Real));
-	/* Can't be both UInt and Int at the same time.  */
-	assert((p->flags & (MEM_Int | MEM_UInt)) != (MEM_Int | MEM_UInt));
-
 	/* The szMalloc field holds the correct memory allocation size */
 	assert(p->szMalloc == 0 ||
 	       p->szMalloc == sqlDbMallocSize(p->db, p->zMalloc));
@@ -1961,7 +2029,7 @@ sqlVdbeCheckMemInvariants(Mem * p)
 	 *   (3) An ephemeral string or blob
 	 *   (4) A static string or blob
 	 */
-	if ((p->flags & (MEM_Str | MEM_Blob)) && p->n > 0) {
+	if ((p->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0 && p->n > 0) {
 		assert(((p->szMalloc > 0 && p->z == p->zMalloc) ? 1 : 0) +
 		       ((p->flags & MEM_Dyn) != 0 ? 1 : 0) +
 		       ((p->flags & MEM_Ephem) != 0 ? 1 : 0) +
@@ -1980,7 +2048,7 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
 	char *zCsr = zBuf;
 	int f = pMem->flags;
 
-	if (f&MEM_Blob) {
+	if (pMem->type == MEM_TYPE_BIN) {
 		int i;
 		char c;
 		if (f & MEM_Dyn) {
@@ -2016,7 +2084,7 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
 			zCsr += sqlStrlen30(zCsr);
 		}
 		*zCsr = '\0';
-	} else if (f & MEM_Str) {
+	} else if (pMem->type == MEM_TYPE_STR) {
 		int j, k;
 		zBuf[0] = ' ';
 		if (f & MEM_Dyn) {
@@ -2056,26 +2124,34 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
 static void
 memTracePrint(Mem *p)
 {
-	if (p->flags & MEM_Undefined) {
-		printf(" undefined");
-	} else if (p->flags & MEM_Null) {
+	switch (p->type) {
+	case MEM_TYPE_NULL:
 		printf(" NULL");
-	} else if ((p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str)) {
-		printf(" si:%lld", p->u.i);
-	} else if (p->flags & MEM_Int) {
+		return;
+	case MEM_TYPE_INT:
 		printf(" i:%lld", p->u.i);
-	} else if (p->flags & MEM_UInt) {
+		return;
+	case MEM_TYPE_UINT:
 		printf(" u:%"PRIu64"", p->u.u);
-	} else if (p->flags & MEM_Real) {
+		return;
+	case MEM_TYPE_DOUBLE:
 		printf(" r:%g", p->u.r);
-	} else if (p->flags & MEM_Bool) {
+		return;
+	case MEM_TYPE_INVALID:
+		printf(" undefined");
+		return;
+	case MEM_TYPE_BOOL:
 		printf(" bool:%s", SQL_TOKEN_BOOLEAN(p->u.b));
-	} else {
+		return;
+	default: {
 		char zBuf[200];
 		sqlVdbeMemPrettyPrint(p, zBuf);
 		printf(" %s", zBuf);
+		if ((p->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
+			printf(" subtype=0x%02x", SQL_SUBTYPE_MSGPACK);
+		return;
+	}
 	}
-	if (p->flags & MEM_Subtype) printf(" subtype=0x%02x", p->subtype);
 }
 
 void
@@ -2095,7 +2171,7 @@ sqlVdbeMemExpandBlob(Mem * pMem)
 {
 	int nByte;
 	assert(pMem->flags & MEM_Zero);
-	assert(pMem->flags & MEM_Blob);
+	assert(pMem->type == MEM_TYPE_BIN);
 
 	/* Set nByte to the number of bytes required to store the expanded blob. */
 	nByte = pMem->n + pMem->u.nZero;
@@ -2121,7 +2197,7 @@ sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
 	/* If the bPreserve flag is set to true, then the memory cell must already
 	 * contain a valid string or blob value.
 	 */
-	assert(bPreserve == 0 || pMem->flags & (MEM_Blob | MEM_Str));
+	assert(bPreserve == 0 || mem_is_bytes(pMem));
 	testcase(bPreserve && pMem->z == 0);
 
 	assert(pMem->szMalloc == 0 ||
@@ -2168,9 +2244,8 @@ sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
  * routine is a no-op.
  *
  * Any prior string or blob content in the pMem object may be discarded.
- * The pMem->xDel destructor is called, if it exists.  Though MEM_Str
- * and MEM_Blob values may be discarded, MEM_Int, MEM_Real, and MEM_Null
- * values are preserved.
+ * The pMem->xDel destructor is called, if it exists. Though STRING, VARBINARY,
+ * MAP and ARRAY values may be discarded, all other values are preserved.
  *
  * Return 0 on success or -1 if unable to complete the resizing.
  */
@@ -2184,7 +2259,6 @@ sqlVdbeMemClearAndResize(Mem * pMem, int szNew)
 	}
 	assert((pMem->flags & MEM_Dyn) == 0);
 	pMem->z = pMem->zMalloc;
-	pMem->flags &= (MEM_Null | MEM_Int | MEM_Real);
 	return 0;
 }
 
@@ -2208,7 +2282,8 @@ sqlValueNew(sql * db)
 {
 	Mem *p = sqlDbMallocZero(db, sizeof(*p));
 	if (p) {
-		p->flags = MEM_Null;
+		p->type = MEM_TYPE_NULL;
+		assert(p->flags == 0);
 		p->db = db;
 	}
 	return p;
@@ -2223,7 +2298,8 @@ releaseMemArray(Mem * p, int N)
 			assert((&p[1]) == pEnd || p[0].db == p[1].db);
 			assert(sqlVdbeCheckMemInvariants(p));
 			mem_destroy(p);
-			p->flags = MEM_Undefined;
+			p->type = MEM_TYPE_INVALID;
+			assert(p->flags == 0);
 		} while ((++p) < pEnd);
 	}
 }
@@ -2236,7 +2312,7 @@ int
 sqlVdbeMemTooBig(Mem * p)
 {
 	assert(p->db != 0);
-	if (p->flags & (MEM_Str | MEM_Blob)) {
+	if (mem_is_bytes(p)) {
 		int n = p->n;
 		if (p->flags & MEM_Zero) {
 			n += p->u.nZero;
@@ -2258,40 +2334,38 @@ sqlVdbeMemTooBig(Mem * p)
 int
 sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
 {
-	int f1, f2;
 	int res;
-	int combined_flags;
 
-	f1 = pMem1->flags;
-	f2 = pMem2->flags;
-	combined_flags = f1 | f2;
+	enum mem_type type1 = pMem1->type;
+	enum mem_type type2 = pMem2->type;
 
 	/* If one value is NULL, it is less than the other. If both values
 	 * are NULL, return 0.
 	 */
-	if (combined_flags & MEM_Null) {
-		return (f2 & MEM_Null) - (f1 & MEM_Null);
-	}
+	if (((type1 | type2) & MEM_TYPE_NULL) != 0)
+		return (int)(type2 == MEM_TYPE_NULL) -
+		       (int)(type1 == MEM_TYPE_NULL);
 
-	if ((combined_flags & MEM_Bool) != 0) {
-		if ((f1 & f2 & MEM_Bool) != 0) {
+	if (((type1 | type2) & MEM_TYPE_BOOL) != 0) {
+		if (type1 == MEM_TYPE_BOOL && type2 == MEM_TYPE_BOOL) {
 			if (pMem1->u.b == pMem2->u.b)
 				return 0;
 			if (pMem1->u.b)
 				return 1;
 			return -1;
 		}
-		if ((f2 & MEM_Bool) != 0)
+		if (type2 == MEM_TYPE_BOOL)
 			return +1;
 		return -1;
 	}
 
 	/* At least one of the two values is a number
 	 */
-	if ((combined_flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0) {
-		if ((f1 & (MEM_Real | MEM_Int | MEM_UInt)) == 0)
+	if (((type1 | type2) &
+	     (MEM_TYPE_INT | MEM_TYPE_UINT | MEM_TYPE_DOUBLE)) != 0) {
+		if (!mem_is_num(pMem1))
 			return +1;
-		if ((f2 & (MEM_Real | MEM_Int | MEM_UInt)) == 0)
+		if (!mem_is_num(pMem2))
 			return -1;
 		mem_cmp_num(pMem1, pMem2, &res);
 		return res;
@@ -2300,11 +2374,11 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
 	/* If one value is a string and the other is a blob, the string is less.
 	 * If both are strings, compare using the collating functions.
 	 */
-	if (combined_flags & MEM_Str) {
-		if ((f1 & MEM_Str) == 0) {
+	if (((type1 | type2) & MEM_TYPE_STR) != 0) {
+		if (type1 != MEM_TYPE_STR) {
 			return 1;
 		}
-		if ((f2 & MEM_Str) == 0) {
+		if (type2 != MEM_TYPE_STR) {
 			return -1;
 		}
 		mem_cmp_str(pMem1, pMem2, &res, pColl);
@@ -2322,12 +2396,13 @@ sql_vdbemem_finalize(struct Mem *mem, struct func *func)
 	assert(func != NULL);
 	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
 	assert(func->def->aggregate == FUNC_AGGREGATE_GROUP);
-	assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
+	assert(mem->type == MEM_TYPE_NULL || func == mem->u.func);
 	sql_context ctx;
 	memset(&ctx, 0, sizeof(ctx));
 	Mem t;
 	memset(&t, 0, sizeof(t));
-	t.flags = MEM_Null;
+	t.type = MEM_TYPE_NULL;
+	t.flags = 0;
 	t.db = mem->db;
 	t.field_type = field_type_MAX;
 	ctx.pOut = &t;
@@ -2356,35 +2431,35 @@ sqlVdbeCompareMsgpack(const char **key1,
 			break;
 		}
 	case MP_NIL:{
-			rc = -((pKey2->flags & MEM_Null) == 0);
+			rc = -(pKey2->type != MEM_TYPE_NULL);
 			mp_decode_nil(&aKey1);
 			break;
 		}
 	case MP_BOOL:{
 			mem1.u.b = mp_decode_bool(&aKey1);
-			if ((pKey2->flags & MEM_Bool) != 0) {
+			if (pKey2->type == MEM_TYPE_BOOL) {
 				if (mem1.u.b != pKey2->u.b)
 					rc = mem1.u.b ? 1 : -1;
 			} else {
-				rc = (pKey2->flags & MEM_Null) != 0 ? 1 : -1;
+				rc = pKey2->type == MEM_TYPE_NULL ? 1 : -1;
 			}
 			break;
 		}
 	case MP_UINT:{
 			mem1.u.u = mp_decode_uint(&aKey1);
-			if ((pKey2->flags & MEM_Int) != 0) {
+			if (pKey2->type == MEM_TYPE_INT) {
 				rc = +1;
-			} else if ((pKey2->flags & MEM_UInt) != 0) {
+			} else if (pKey2->type == MEM_TYPE_UINT) {
 				if (mem1.u.u < pKey2->u.u)
 					rc = -1;
 				else if (mem1.u.u > pKey2->u.u)
 					rc = +1;
-			} else if ((pKey2->flags & MEM_Real) != 0) {
+			} else if (pKey2->type == MEM_TYPE_DOUBLE) {
 				rc = double_compare_uint64(pKey2->u.r,
 							   mem1.u.u, -1);
-			} else if ((pKey2->flags & MEM_Null) != 0) {
+			} else if (pKey2->type == MEM_TYPE_NULL) {
 				rc = 1;
-			} else if ((pKey2->flags & MEM_Bool) != 0) {
+			} else if (pKey2->type == MEM_TYPE_BOOL) {
 				rc = 1;
 			} else {
 				rc = -1;
@@ -2393,20 +2468,20 @@ sqlVdbeCompareMsgpack(const char **key1,
 		}
 	case MP_INT:{
 			mem1.u.i = mp_decode_int(&aKey1);
-			if ((pKey2->flags & MEM_UInt) != 0) {
+			if (pKey2->type == MEM_TYPE_UINT) {
 				rc = -1;
-			} else if ((pKey2->flags & MEM_Int) != 0) {
+			} else if (pKey2->type == MEM_TYPE_INT) {
 				if (mem1.u.i < pKey2->u.i) {
 					rc = -1;
 				} else if (mem1.u.i > pKey2->u.i) {
 					rc = +1;
 				}
-			} else if (pKey2->flags & MEM_Real) {
+			} else if (pKey2->type == MEM_TYPE_DOUBLE) {
 				rc = double_compare_nint64(pKey2->u.r, mem1.u.i,
 							   -1);
-			} else if ((pKey2->flags & MEM_Null) != 0) {
+			} else if (pKey2->type == MEM_TYPE_NULL) {
 				rc = 1;
-			} else if ((pKey2->flags & MEM_Bool) != 0) {
+			} else if (pKey2->type == MEM_TYPE_BOOL) {
 				rc = 1;
 			} else {
 				rc = -1;
@@ -2420,21 +2495,21 @@ sqlVdbeCompareMsgpack(const char **key1,
 	case MP_DOUBLE:{
 			mem1.u.r = mp_decode_double(&aKey1);
  do_float:
-			if ((pKey2->flags & MEM_Int) != 0) {
+			if (pKey2->type == MEM_TYPE_INT) {
 				rc = double_compare_nint64(mem1.u.r, pKey2->u.i,
 							   1);
-			} else if (pKey2->flags & MEM_UInt) {
+			} else if (pKey2->type == MEM_TYPE_UINT) {
 				rc = double_compare_uint64(mem1.u.r,
 							   pKey2->u.u, 1);
-			} else if (pKey2->flags & MEM_Real) {
+			} else if (pKey2->type == MEM_TYPE_DOUBLE) {
 				if (mem1.u.r < pKey2->u.r) {
 					rc = -1;
 				} else if (mem1.u.r > pKey2->u.r) {
 					rc = +1;
 				}
-			} else if ((pKey2->flags & MEM_Null) != 0) {
+			} else if (pKey2->type == MEM_TYPE_NULL) {
 				rc = 1;
-			} else if ((pKey2->flags & MEM_Bool) != 0) {
+			} else if (pKey2->type == MEM_TYPE_BOOL) {
 				rc = 1;
 			} else {
 				rc = -1;
@@ -2442,7 +2517,7 @@ sqlVdbeCompareMsgpack(const char **key1,
 			break;
 		}
 	case MP_STR:{
-			if (pKey2->flags & MEM_Str) {
+			if (pKey2->type == MEM_TYPE_STR) {
 				struct key_def *key_def = unpacked->key_def;
 				mem1.n = mp_decode_strl(&aKey1);
 				mem1.z = (char *)aKey1;
@@ -2450,14 +2525,15 @@ sqlVdbeCompareMsgpack(const char **key1,
 				struct coll *coll =
 					key_def->parts[key2_idx].coll;
 				if (coll != NULL) {
-					mem1.flags = MEM_Str;
+					mem1.type = MEM_TYPE_STR;
+					mem1.flags = 0;
 					rc = vdbeCompareMemString(&mem1, pKey2,
 								  coll);
 				} else {
 					goto do_bin_cmp;
 				}
 			} else {
-				rc = (pKey2->flags & MEM_Blob) ? -1 : +1;
+				rc = pKey2->type == MEM_TYPE_BIN ? -1 : +1;
 			}
 			break;
 		}
@@ -2466,7 +2542,7 @@ sqlVdbeCompareMsgpack(const char **key1,
 			mem1.z = (char *)aKey1;
 			aKey1 += mem1.n;
  do_blob:
-			if (pKey2->flags & MEM_Blob) {
+			if (pKey2->type == MEM_TYPE_BIN) {
 				if (pKey2->flags & MEM_Zero) {
 					if (!isAllZero
 					    ((const char *)mem1.z, mem1.n)) {
@@ -2533,8 +2609,8 @@ mem_from_mp_ephemeral(struct Mem *mem, const char *buf, uint32_t *len)
 		mem->z = (char *)buf;
 		mp_next(&buf);
 		mem->n = buf - mem->z;
-		mem->flags = MEM_Blob | MEM_Ephem | MEM_Subtype;
-		mem->subtype = SQL_SUBTYPE_MSGPACK;
+		mem->type = MEM_TYPE_ARRAY;
+		mem->flags = MEM_Ephem;
 		mem->field_type = FIELD_TYPE_ARRAY;
 		break;
 	}
@@ -2542,8 +2618,8 @@ mem_from_mp_ephemeral(struct Mem *mem, const char *buf, uint32_t *len)
 		mem->z = (char *)buf;
 		mp_next(&buf);
 		mem->n = buf - mem->z;
-		mem->flags = MEM_Blob | MEM_Ephem | MEM_Subtype;
-		mem->subtype = SQL_SUBTYPE_MSGPACK;
+		mem->type = MEM_TYPE_MAP;
+		mem->flags = MEM_Ephem;
 		mem->field_type = FIELD_TYPE_MAP;
 		break;
 	}
@@ -2551,39 +2627,45 @@ mem_from_mp_ephemeral(struct Mem *mem, const char *buf, uint32_t *len)
 		mem->z = (char *)buf;
 		mp_next(&buf);
 		mem->n = buf - mem->z;
-		mem->flags = MEM_Blob | MEM_Ephem;
+		mem->type = MEM_TYPE_BIN;
+		mem->flags = MEM_Ephem;
 		mem->field_type = FIELD_TYPE_VARBINARY;
 		break;
 	}
 	case MP_NIL: {
 		mp_decode_nil(&buf);
-		mem->flags = MEM_Null;
+		mem->type = MEM_TYPE_NULL;
+		mem->flags = 0;
 		mem->field_type = field_type_MAX;
 		break;
 	}
 	case MP_BOOL: {
 		mem->u.b = mp_decode_bool(&buf);
-		mem->flags = MEM_Bool;
+		mem->type = MEM_TYPE_BOOL;
+		mem->flags = 0;
 		mem->field_type = FIELD_TYPE_BOOLEAN;
 		break;
 	}
 	case MP_UINT: {
 		uint64_t v = mp_decode_uint(&buf);
 		mem->u.u = v;
-		mem->flags = MEM_UInt;
+		mem->type = MEM_TYPE_UINT;
+		mem->flags = 0;
 		mem->field_type = FIELD_TYPE_INTEGER;
 		break;
 	}
 	case MP_INT: {
 		mem->u.i = mp_decode_int(&buf);
-		mem->flags = MEM_Int;
+		mem->type = MEM_TYPE_INT;
+		mem->flags = 0;
 		mem->field_type = FIELD_TYPE_INTEGER;
 		break;
 	}
 	case MP_STR: {
 		/* XXX u32->int */
 		mem->n = (int) mp_decode_strl(&buf);
-		mem->flags = MEM_Str | MEM_Ephem;
+		mem->type = MEM_TYPE_STR;
+		mem->flags = MEM_Ephem;
 		mem->field_type = FIELD_TYPE_STRING;
 install_blob:
 		mem->z = (char *)buf;
@@ -2593,17 +2675,20 @@ install_blob:
 	case MP_BIN: {
 		/* XXX u32->int */
 		mem->n = (int) mp_decode_binl(&buf);
-		mem->flags = MEM_Blob | MEM_Ephem;
+		mem->type = MEM_TYPE_BIN;
+		mem->flags = MEM_Ephem;
 		mem->field_type = FIELD_TYPE_VARBINARY;
 		goto install_blob;
 	}
 	case MP_FLOAT: {
 		mem->u.r = mp_decode_float(&buf);
 		if (sqlIsNaN(mem->u.r)) {
-			mem->flags = MEM_Null;
+			mem->type = MEM_TYPE_NULL;
+			mem->flags = 0;
 			mem->field_type = FIELD_TYPE_DOUBLE;
 		} else {
-			mem->flags = MEM_Real;
+			mem->type = MEM_TYPE_DOUBLE;
+			mem->flags = 0;
 			mem->field_type = FIELD_TYPE_DOUBLE;
 		}
 		break;
@@ -2611,10 +2696,12 @@ install_blob:
 	case MP_DOUBLE: {
 		mem->u.r = mp_decode_double(&buf);
 		if (sqlIsNaN(mem->u.r)) {
-			mem->flags = MEM_Null;
+			mem->type = MEM_TYPE_NULL;
+			mem->flags = 0;
 			mem->field_type = FIELD_TYPE_DOUBLE;
 		} else {
-			mem->flags = MEM_Real;
+			mem->type = MEM_TYPE_DOUBLE;
+			mem->flags = 0;
 			mem->field_type = FIELD_TYPE_DOUBLE;
 		}
 		break;
@@ -2631,7 +2718,7 @@ mem_from_mp(struct Mem *mem, const char *buf, uint32_t *len)
 {
 	if (mem_from_mp_ephemeral(mem, buf, len) != 0)
 		return -1;
-	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
+	if (mem_is_bytes(mem)) {
 		assert((mem->flags & MEM_Ephem) != 0);
 		if (sqlVdbeMemGrow(mem, mem->n, 1) != 0)
 			return -1;
@@ -2643,35 +2730,41 @@ void
 mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var)
 {
 	assert(memIsValid(var));
-	int64_t i;
-	if (var->flags & MEM_Null) {
+	switch (var->type) {
+	case MEM_TYPE_NULL:
 		mpstream_encode_nil(stream);
-	} else if (var->flags & MEM_Real) {
-		mpstream_encode_double(stream, var->u.r);
-	} else if (var->flags & MEM_Int) {
-		i = var->u.i;
-		mpstream_encode_int(stream, i);
-	} else if (var->flags & MEM_UInt) {
-		i = var->u.u;
-		mpstream_encode_uint(stream, i);
-	} else if (var->flags & MEM_Str) {
+		return;
+	case MEM_TYPE_STR:
 		mpstream_encode_strn(stream, var->z, var->n);
-	} else if (var->flags & MEM_Bool) {
-		mpstream_encode_bool(stream, var->u.b);
-	} else {
-		/*
-		 * Emit BIN header iff the BLOB doesn't store
-		 * MsgPack content.
-		 */
-		if (!mem_has_msgpack_subtype(var)) {
-			uint32_t binl = var->n +
-					((var->flags & MEM_Zero) ?
-					var->u.nZero : 0);
-			mpstream_encode_binl(stream, binl);
+		return;
+	case MEM_TYPE_INT:
+		mpstream_encode_int(stream, var->u.i);
+		return;
+	case MEM_TYPE_UINT:
+		mpstream_encode_uint(stream, var->u.u);
+		return;
+	case MEM_TYPE_DOUBLE:
+		mpstream_encode_double(stream, var->u.r);
+		return;
+	case MEM_TYPE_BIN:
+		if ((var->flags & MEM_Zero) != 0) {
+			mpstream_encode_binl(stream, var->n + var->u.nZero);
+			mpstream_memcpy(stream, var->z, var->n);
+			mpstream_memset(stream, 0, var->u.nZero);
+		} else {
+			mpstream_encode_binl(stream, var->n);
+			mpstream_memcpy(stream, var->z, var->n);
 		}
+		return;
+	case MEM_TYPE_ARRAY:
+	case MEM_TYPE_MAP:
 		mpstream_memcpy(stream, var->z, var->n);
-		if (var->flags & MEM_Zero)
-			mpstream_memset(stream, 0, var->u.nZero);
+		return;
+	case MEM_TYPE_BOOL:
+		mpstream_encode_bool(stream, var->u.b);
+		return;
+	default:
+		unreachable();
 	}
 }
 
@@ -2734,24 +2827,26 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
 	assert(is_flat == true);
 	for (uint32_t i = 0; i < port->mem_count; i++) {
 		struct Mem *mem = (struct Mem *)port->mem + i;
-		switch (mem->flags & MEM_PURE_TYPE_MASK) {
-		case MEM_Int:
+		switch (mem->type) {
+		case MEM_TYPE_INT:
 			luaL_pushint64(L, mem->u.i);
 			break;
-		case MEM_UInt:
+		case MEM_TYPE_UINT:
 			luaL_pushuint64(L, mem->u.u);
 			break;
-		case MEM_Real:
+		case MEM_TYPE_DOUBLE:
 			lua_pushnumber(L, mem->u.r);
 			break;
-		case MEM_Str:
-		case MEM_Blob:
+		case MEM_TYPE_STR:
+		case MEM_TYPE_BIN:
+		case MEM_TYPE_MAP:
+		case MEM_TYPE_ARRAY:
 			lua_pushlstring(L, mem->z, mem->n);
 			break;
-		case MEM_Null:
+		case MEM_TYPE_NULL:
 			lua_pushnil(L);
 			break;
-		case MEM_Bool:
+		case MEM_TYPE_BOOL:
 			lua_pushboolean(L, mem->u.b);
 			break;
 		default:
@@ -2844,23 +2939,28 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size)
 		mem_clear(&val[i]);
 		switch (field.type) {
 		case MP_BOOL:
-			val[i].flags = MEM_Bool;
+			val[i].type = MEM_TYPE_BOOL;
+			assert(val[i].flags == 0);
 			val[i].u.b = field.bval;
 			break;
 		case MP_FLOAT:
-			val[i].flags = MEM_Real;
+			val[i].type = MEM_TYPE_DOUBLE;
+			assert(val[i].flags == 0);
 			val[i].u.r = field.fval;
 			break;
 		case MP_DOUBLE:
-			val[i].flags = MEM_Real;
+			val[i].type = MEM_TYPE_DOUBLE;
+			assert(val[i].flags == 0);
 			val[i].u.r = field.dval;
 			break;
 		case MP_INT:
-			val[i].flags = MEM_Int;
+			val[i].type = MEM_TYPE_INT;
+			assert(val[i].flags == 0);
 			val[i].u.i = field.ival;
 			break;
 		case MP_UINT:
-			val[i].flags = MEM_UInt;
+			val[i].type = MEM_TYPE_UINT;
+			assert(val[i].flags == 0);
 			val[i].u.i = field.ival;
 			break;
 		case MP_STR:
@@ -2919,23 +3019,28 @@ port_c_get_vdbemem(struct port *base, uint32_t *size)
 		const char *str;
 		switch (mp_typeof(*data)) {
 		case MP_BOOL:
-			val[i].flags = MEM_Bool;
+			val[i].type = MEM_TYPE_BOOL;
+			assert(val[i].flags == 0);
 			val[i].u.b = mp_decode_bool(&data);
 			break;
 		case MP_FLOAT:
-			val[i].flags = MEM_Real;
+			val[i].type = MEM_TYPE_DOUBLE;
+			assert(val[i].flags == 0);
 			val[i].u.r = mp_decode_float(&data);
 			break;
 		case MP_DOUBLE:
-			val[i].flags = MEM_Real;
+			val[i].type = MEM_TYPE_DOUBLE;
+			assert(val[i].flags == 0);
 			val[i].u.r = mp_decode_double(&data);
 			break;
 		case MP_INT:
-			val[i].flags = MEM_Int;
+			val[i].type = MEM_TYPE_INT;
+			assert(val[i].flags == 0);
 			val[i].u.i = mp_decode_int(&data);
 			break;
 		case MP_UINT:
-			val[i].flags = MEM_UInt;
+			val[i].type = MEM_TYPE_UINT;
+			assert(val[i].flags == 0);
 			val[i].u.u = mp_decode_uint(&data);
 			break;
 		case MP_STR:
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 526b6bf3e..29d373cfd 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -37,6 +37,23 @@ struct region;
 struct mpstream;
 struct VdbeFrame;
 
+enum mem_type {
+	MEM_TYPE_NULL		= 1,
+	MEM_TYPE_UINT		= 1 << 1,
+	MEM_TYPE_INT		= 1 << 2,
+	MEM_TYPE_STR		= 1 << 3,
+	MEM_TYPE_BIN		= 1 << 4,
+	MEM_TYPE_ARRAY		= 1 << 5,
+	MEM_TYPE_MAP		= 1 << 6,
+	MEM_TYPE_BOOL		= 1 << 7,
+	MEM_TYPE_FLOAT		= 1 << 8,
+	MEM_TYPE_DOUBLE		= 1 << 9,
+	MEM_TYPE_INVALID	= 1 << 10,
+	MEM_TYPE_FRAME		= 1 << 11,
+	MEM_TYPE_PTR		= 1 << 12,
+	MEM_TYPE_AGG		= 1 << 13,
+};
+
 /*
  * Internally, the vdbe manipulates nearly all SQL values as Mem
  * structures. Each Mem struct may cache multiple representations (string,
@@ -57,6 +74,8 @@ struct Mem {
 		struct func *func;
 		struct VdbeFrame *pFrame;	/* Used when flags==MEM_Frame */
 	} u;
+	/** Type of the value this MEM contains. */
+	enum mem_type type;
 	u32 flags;		/* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
 	/** Subtype for this value. */
 	enum sql_subtype subtype;
@@ -81,29 +100,7 @@ struct Mem {
 #endif
 };
 
-/* One or more of the following flags are set to indicate the validOK
- * representations of the value stored in the Mem struct.
- *
- * If the MEM_Null flag is set, then the value is an SQL NULL value.
- * No other flags may be set in this case.
- *
- * If the MEM_Str flag is set then Mem.z points at a string representation.
- * Usually this is encoded in the same unicode encoding as the main
- * database (see below for exceptions). If the MEM_Term flag is also
- * set, then the string is nul terminated. The MEM_Int and MEM_Real
- * flags may coexist with the MEM_Str flag.
- */
-#define MEM_Null      0x0001	/* Value is NULL */
-#define MEM_Str       0x0002	/* Value is a string */
-#define MEM_Int       0x0004	/* Value is an integer */
-#define MEM_Real      0x0008	/* Value is a real number */
-#define MEM_Blob      0x0010	/* Value is a BLOB */
-#define MEM_Bool      0x0020    /* Value is a bool */
-#define MEM_UInt      0x0040	/* Value is an unsigned integer */
-#define MEM_Frame     0x0080	/* Value is a VdbeFrame object */
-#define MEM_Undefined 0x0100	/* Value is undefined */
 #define MEM_Cleared   0x0200	/* NULL set by OP_Null, not from data */
-#define MEM_TypeMask  0x83ff	/* Mask of type bits */
 
 /* Whenever Mem contains a valid string or blob representation, one of
  * the following flags must be set to determine the memory management
@@ -114,175 +111,151 @@ struct Mem {
 #define MEM_Dyn       0x0800	/* Need to call Mem.xDel() on Mem.z */
 #define MEM_Static    0x1000	/* Mem.z points to a static string */
 #define MEM_Ephem     0x2000	/* Mem.z points to an ephemeral string */
-#define MEM_Agg       0x4000	/* Mem.z points to an agg function context */
 #define MEM_Zero      0x8000	/* Mem.i contains count of 0s appended to blob */
-#define MEM_Subtype   0x10000	/* Mem.eSubtype is valid */
-#define MEM_Ptr       0x20000	/* Value is a generic pointer */
-
-/**
- * In contrast to Mem_TypeMask, this one allows to get
- * pure type of memory cell, i.e. without _Dyn/_Zero and other
- * auxiliary flags.
- */
-enum {
-	MEM_PURE_TYPE_MASK = 0x7f
-};
-
-static_assert(MEM_PURE_TYPE_MASK == (MEM_Null | MEM_Str | MEM_Int | MEM_Real |
-				     MEM_Blob | MEM_Bool | MEM_UInt),
-	      "value of type mask must consist of corresponding to memory "\
-	      "type bits");
 
 static inline bool
 mem_is_null(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Null) != 0;
+	return mem->type == MEM_TYPE_NULL;
 }
 
 static inline bool
 mem_is_uint(const struct Mem *mem)
 {
-	return (mem->flags & MEM_UInt) != 0;
+	return mem->type == MEM_TYPE_UINT;
 }
 
 static inline bool
 mem_is_nint(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Int) != 0;
+	return mem->type == MEM_TYPE_INT;
 }
 
 static inline bool
 mem_is_str(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Str) != 0;
+	return mem->type == MEM_TYPE_STR;
 }
 
 static inline bool
 mem_is_num(const struct Mem *mem)
 {
-	return (mem->flags & (MEM_Real | MEM_Int | MEM_UInt)) != 0;
+	enum mem_type type = mem->type;
+	return (type & (MEM_TYPE_UINT | MEM_TYPE_INT | MEM_TYPE_DOUBLE)) != 0;
 }
 
 static inline bool
 mem_is_double(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Real) != 0;
+	return mem->type == MEM_TYPE_DOUBLE;
 }
 
 static inline bool
 mem_is_int(const struct Mem *mem)
 {
-	return (mem->flags & (MEM_Int | MEM_UInt)) != 0;
+	return (mem->type & (MEM_TYPE_UINT | MEM_TYPE_INT)) != 0;
 }
 
 static inline bool
 mem_is_bool(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Bool) != 0;
+	return mem->type == MEM_TYPE_BOOL;
 }
 
 static inline bool
 mem_is_bin(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Blob) != 0 && (mem->flags & MEM_Subtype) == 0;
+	return mem->type == MEM_TYPE_BIN;
 }
 
 static inline bool
 mem_is_map(const struct Mem *mem)
 {
-	assert((mem->flags & MEM_Subtype) == 0 || (mem->flags & MEM_Blob) != 0);
-	assert((mem->flags & MEM_Subtype) == 0 ||
-	       mem->subtype == SQL_SUBTYPE_MSGPACK);
-	return (mem->flags & MEM_Subtype) != 0 && mp_typeof(*mem->z) == MP_MAP;
+	return mem->type == MEM_TYPE_MAP;
 }
 
 static inline bool
 mem_is_array(const struct Mem *mem)
 {
-	assert((mem->flags & MEM_Subtype) == 0 || (mem->flags & MEM_Blob) != 0);
-	assert((mem->flags & MEM_Subtype) == 0 ||
-	       mem->subtype == SQL_SUBTYPE_MSGPACK);
-	return (mem->flags & MEM_Subtype) != 0 &&
-	       mp_typeof(*mem->z) == MP_ARRAY;
+	return mem->type == MEM_TYPE_ARRAY;
 }
 
 static inline bool
 mem_is_agg(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Agg) != 0;
+	return mem->type == MEM_TYPE_AGG;
 }
 
 static inline bool
 mem_is_bytes(const struct Mem *mem)
 {
-	return (mem->flags & (MEM_Blob | MEM_Str)) != 0;
+	return (mem->type & (MEM_TYPE_BIN | MEM_TYPE_STR |
+			     MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0;
 }
 
 static inline bool
 mem_is_frame(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Frame) != 0;
+	return mem->type == MEM_TYPE_FRAME;
 }
 
 static inline bool
 mem_is_invalid(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Undefined) != 0;
+	return mem->type == MEM_TYPE_INVALID;
 }
 
 static inline bool
 mem_is_static(const struct Mem *mem)
 {
-	assert((mem->flags & (MEM_Str | MEM_Blob)) != 0);
+	assert(mem_is_bytes(mem));
 	return (mem->flags & MEM_Static) != 0;
 }
 
 static inline bool
 mem_is_ephemeral(const struct Mem *mem)
 {
-	assert((mem->flags & (MEM_Str | MEM_Blob)) != 0);
+	assert(mem_is_bytes(mem));
 	return (mem->flags & MEM_Ephem) != 0;
 }
 
 static inline bool
 mem_is_dynamic(const struct Mem *mem)
 {
-	assert((mem->flags & (MEM_Str | MEM_Blob)) != 0);
+	assert(mem_is_bytes(mem));
 	return (mem->flags & MEM_Dyn) != 0;
 }
 
 static inline bool
 mem_is_allocated(const struct Mem *mem)
 {
-	return (mem->flags & (MEM_Str | MEM_Blob)) != 0 &&
-	       mem->z == mem->zMalloc;
+	return mem_is_bytes(mem) && mem->z == mem->zMalloc;
 }
 
 static inline bool
 mem_is_cleared(const struct Mem *mem)
 {
-	assert((mem->flags & MEM_Cleared) == 0 || (mem->flags & MEM_Null) != 0);
+	assert((mem->flags & MEM_Cleared) == 0 || mem->type == MEM_TYPE_NULL);
 	return (mem->flags & MEM_Cleared) != 0;
 }
 
 static inline bool
 mem_is_zerobin(const struct Mem *mem)
 {
-	assert((mem->flags & MEM_Zero) == 0 || (mem->flags & MEM_Blob) != 0);
+	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
 	return (mem->flags & MEM_Zero) != 0;
 }
 
 static inline bool
 mem_is_same_type(const struct Mem *mem1, const struct Mem *mem2)
 {
-	return (mem1->flags & MEM_PURE_TYPE_MASK) ==
-	       (mem2->flags & MEM_PURE_TYPE_MASK);
+	return mem1->type == mem2->type;
 }
 
 static inline bool
 mem_is_any_null(const struct Mem *mem1, const struct Mem *mem2)
 {
-	return ((mem1->flags | mem2->flags) & MEM_Null) != 0;
+	return ((mem1->type| mem2->type) & MEM_TYPE_NULL) != 0;
 }
 
 /**
@@ -943,7 +916,7 @@ registerTrace(int iReg, Mem *p);
  * Return true if a memory cell is not marked as invalid.  This macro
  * is for use inside assert() statements only.
  */
-#define memIsValid(M)  ((M)->flags & MEM_Undefined)==0
+#define memIsValid(M)  ((M)->type != MEM_TYPE_INVALID)
 #endif
 
 int sqlVdbeMemExpandBlob(struct Mem *);
@@ -969,9 +942,8 @@ int sqlVdbeMemTooBig(Mem *);
 /* Return TRUE if Mem X contains dynamically allocated content - anything
  * that needs to be deallocated to avoid a leak.
  */
-#define VdbeMemDynamic(X)  \
-  (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_Frame))!=0)
-
+#define VdbeMemDynamic(X) (((X)->flags & MEM_Dyn) != 0 ||\
+			   ((X)->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0)
 
 int sqlMemCompare(const Mem *, const Mem *, const struct coll *);
 
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index ba5c08a00..1aa201466 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -93,7 +93,8 @@ valueNew(sql * db, struct ValueNewStat4Ctx *p)
 			pRec->aMem = (Mem *)((char *) pRec +
 					     ROUND8(sizeof(UnpackedRecord)));
 			for (uint32_t i = 0; i < part_count; i++) {
-				pRec->aMem[i].flags = MEM_Null;
+				pRec->aMem[i].type = MEM_NULL;
+				pRec->aMem[i].flags = 0;
 				pRec->aMem[i].db = db;
 			}
 			p->ppRec[0] = pRec;

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

* Re: [Tarantool-patches] [PATCH v2 3/3] sql: replace MEM-type flags by enum mem_type
  2021-05-17 12:18     ` Mergen Imeev via Tarantool-patches
@ 2021-05-17 12:34       ` Mergen Imeev via Tarantool-patches
  2021-05-21 18:59       ` Vladislav Shpilevoy via Tarantool-patches
  1 sibling, 0 replies; 11+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-05-17 12:34 UTC (permalink / raw)
  To: Vladislav Shpilevoy, tarantool-patches

Sorry, along with reverting changes of struct MEM I also reverted removal of 
subtype' field. I removed this field. Diff below.

Diff:

diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 29d373cfd..204d98129 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -77,8 +77,6 @@ struct Mem {
 	/** Type of the value this MEM contains. */
 	enum mem_type type;
 	u32 flags;		/* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
-	/** Subtype for this value. */
-	enum sql_subtype subtype;
 	/**
 	 * If value is fetched from tuple, then this property
 	 * contains type of corresponding space's field. If it's



On Mon, May 17, 2021 at 03:18:11PM +0300, Mergen Imeev via Tarantool-patches wrote:
> Thank you for the review! My answers, diff and new patch below.
> 
> On Thu, Apr 29, 2021 at 11:09:43PM +0200, Vladislav Shpilevoy wrote:
> > I appreciate the work you did here!
> > 
> > >>>  	mem->field_type = FIELD_TYPE_VARBINARY;
> > >>>  	return 0;
> > >>>  }> @@ -1168,9 +1229,9 @@ mem_get_bin(const struct Mem *mem, const char **s)
> > >>>  int
> > >>>  mem_len(const struct Mem *mem, uint32_t *len)
> > >>>  {
> > >>> -	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
> > >>> +	if (mem->type != MEM_STR && mem->type != MEM_BIN)
> > >>>  		return -1;
> > >>
> > >> 8. Is it -1 for MAP and ARRAY intentionally? Previously they
> > >> were included into MEM_Blob.
> > >>
> > >> I see mem_concat() didn't check for subtypes. Does it mean we
> > >> could concat two MP_ARRAYs into something invalid? If we could,
> > >> your patch probably just fixed it. Could you check and add a
> > >> test if so?
> > >>
> > > Fixed. You were right, it is actually possible to concatenate two maps, map and
> > > array, map and varbinary and so on. I returned ability to do this. Should I add
> > > a test?
> > 
> > That behaviour is not correct, and should raise an error I think. You
> > can add a test, but if it works now, then it should have a comment
> > saying that this test is bad and must start failing in the future. And
> > now you might test it just to ensure it does not crash anywhere.
> > 
> I removed ability to compare something with MAP/ARRAY and prohibited to
> concatenate values of type MAP and ARRAY. I did not add a test, since currently
> behasiour of MAP and ARRAY is not properly defined. I described this in the
> previous letter.
> 
> > >>> @@ -114,175 +120,153 @@ struct Mem {
> > >>
> > >> <...>
> > >>
> > >>>  static inline bool
> > >>>  mem_is_agg(const struct Mem *mem)
> > >>>  {
> > >>> -	return (mem->flags & MEM_Agg) != 0;
> > >>> +	return mem->type == MEM_AGG;
> > >>>  }
> > >>>  
> > >>>  static inline bool
> > >>>  mem_is_bytes(const struct Mem *mem)
> > >>>  {
> > >>> -	return (mem->flags & (MEM_Blob | MEM_Str)) != 0;
> > >>> +	enum mem_type type = mem->type;
> > >>> +	return type == MEM_BIN || type == MEM_MAP || type == MEM_ARRAY ||
> > >>> +	       type == MEM_STR;
> > >>>  }
> > >> 15. It was good when we could check several rare cases at one
> > >> branch. Maybe you could preserve the bitwise structure of the
> > >> types? Then we could keep the bit operations and save a few
> > >> branches. You didn't think of it, or do we go for normal numbers
> > >> intentionally?
> > >>
> > > I did it, although I'm not sure if this is the right choice: although it makes
> > > our code more compact in some cases, it makes it a little less readable.
> > 
> > I just realized another issue which you will get with UUID and DECIMAL. They
> > are not present in mp_type, so there won't be a direct mapping anyway. They
> > are encoded as MP_EXT + MP_UUID/MP_DECIMAL. It fits into a 64bit integer,
> > but it is a binary header, and is not in mp_type value range. The same for
> > MEM_TYPE_PTR, MEM_TYPE_FRAME, and other SQL-specific.
> > 
> > See 5 comments below.
> > 
> > > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > > index b6ff6397f..4f189cac4 100644
> > > --- a/src/box/sql/mem.c
> > > +++ b/src/box/sql/mem.c
> > > @@ -89,39 +89,36 @@ mem_str(const struct Mem *mem)
> > 
> > <...>
> > 
> > >  static inline void
> > >  mem_clear(struct Mem *mem)
> > >  {
> > > -	if ((mem->flags & (MEM_Agg | MEM_Dyn | MEM_Frame)) != 0) {
> > > -		if ((mem->flags & MEM_Agg) != 0)
> > > -			sql_vdbemem_finalize(mem, mem->u.func);
> > > -		assert((mem->flags & MEM_Agg) == 0);
> > > -		if ((mem->flags & MEM_Dyn) != 0) {
> > > -			assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
> > > -			mem->xDel((void *)mem->z);
> > > -		} else if ((mem->flags & MEM_Frame) != 0) {
> > > -			struct VdbeFrame *frame = mem->u.pFrame;
> > > -			frame->pParent = frame->v->pDelFrame;
> > > -			frame->v->pDelFrame = frame;
> > > -		}
> > > -	}
> > > -	mem->flags = MEM_Null;
> > > +	if (mem->type == MEM_TYPE_AGG) {
> > > +		sql_vdbemem_finalize(mem, mem->u.func);
> > > +		assert(mem->type != MEM_TYPE_AGG);
> > > +	} else if (mem->type == MEM_TYPE_FRAME) {
> > > +		struct VdbeFrame *frame = mem->u.pFrame;
> > > +		frame->pParent = frame->v->pDelFrame;
> > > +		frame->v->pDelFrame = frame;
> > > +	} else if ((mem->flags & MEM_Dyn) != 0) {
> > > +		assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
> > > +		mem->xDel((void *)mem->z);
> > > +	}
> > 
> > 1. With the new bitwise structure you could reduce the diff.
> > For example, here there was one 'if' for a non-common case of
> > something allocated dynamically. Now there are 3 'if's.
> > 
> Fixed, still now there is two conditions in if instead of one - one condition
> for field type and one for field flags.
> 
> > > +	mem->type = MEM_TYPE_NULL;
> > > +	mem->flags = 0;
> > >  	mem->field_type = field_type_MAX;
> > >  }
> > > @@ -268,11 +271,13 @@ mem_set_str0_allocated(struct Mem *mem, char *value)
> > >  int
> > >  mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
> > >  {
> > > -	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0 && mem->z == value) {
> > > +	if ((mem->type == MEM_TYPE_STR ||
> > > +	     mem->type == MEM_TYPE_BIN) && mem->z == value) {
> > 
> > 2. The only reason I proposed to keep the types in their
> > own bits was to reduce number of the branches in the 'if's.
> > So as you could write flags & (MEM_TYPE_STR | MEM_TYPE_BIN) != 0
> > for checking for multiple types instead of using || which adds
> > an implicit branch.
> > 
> Sorry, I wasn't able to fully interpret this. I think now I understand.
> 
> > The same in mem_copy_bin(), mem_to_int(), mem_to_int_precise(),
> > mem_to_double(), mem_to_number(), end of mem_cast_explicit()
> > (the FIELD_TYPE_SCALAR case), in a few places in mem_cast_implicit(),
> > mem_cast_implicit_old(), mem_get_int(), mem_get_uint(), try_return_null(),
> > mem_concat(), get_number(), mem_cmp_bin(), sqlVdbeCheckMemInvariants(),
> > end of memTracePrint(). Maybe more which I missed.
> > 
> I think I fixed the problem in all these functions and in some other.
> 
> > >  		/* Own value, but might be ephemeral. Make it own if so. */
> > >  		if (sqlVdbeMemGrow(mem, len, 1) != 0)
> > >  			return -1;
> > > -		mem->flags = MEM_Str;
> > > +		mem->type = MEM_TYPE_STR;
> > > +		mem->flags = 0;
> > >  		mem->field_type = FIELD_TYPE_STRING;
> > >  		return 0;
> > >  	}
> > > @@ -2258,40 +2330,37 @@ sqlVdbeMemTooBig(Mem * p)
> > >  int
> > >  sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
> > >  {
> > > -	int f1, f2;
> > >  	int res;
> > > -	int combined_flags;
> > >  
> > > -	f1 = pMem1->flags;
> > > -	f2 = pMem2->flags;
> > > -	combined_flags = f1 | f2;
> > > +	enum mem_type type1 = pMem1->type;
> > > +	enum mem_type type2 = pMem2->type;
> > >  
> > >  	/* If one value is NULL, it is less than the other. If both values
> > >  	 * are NULL, return 0.
> > >  	 */
> > > -	if (combined_flags & MEM_Null) {
> > > -		return (f2 & MEM_Null) - (f1 & MEM_Null);
> > > -	}
> > > +	if (type1 == MEM_TYPE_NULL || type2 == MEM_TYPE_NULL)
> > 
> > 3. With the bitwise types you could have combined_types and
> > keep only one branch in such places.
> > 
> Fixed.
> 
> > > +		return (int)(type2 == MEM_TYPE_NULL) -
> > > +		       (int)(type1 == MEM_TYPE_NULL);
> > >  
> > > diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> > > index 6fc15617d..a4a0d2223 100644
> > > --- a/src/box/sql/mem.h
> > > +++ b/src/box/sql/mem.h
> > > @@ -37,73 +37,78 @@ struct region;
> > >  struct mpstream;
> > >  struct VdbeFrame;
> > >  
> > > -/*
> > > - * Internally, the vdbe manipulates nearly all SQL values as Mem
> > > - * structures. Each Mem struct may cache multiple representations (string,
> > > - * integer etc.) of the same value.
> > > - */
> > > +enum mem_type {
> > > +	MEM_TYPE_NULL = 1u << MP_NIL,
> > > +	MEM_TYPE_UINT = 1u << MP_UINT,
> > > +	MEM_TYPE_INT = 1u << MP_INT,
> > > +	MEM_TYPE_STR = 1u << MP_STR,
> > > +	MEM_TYPE_BIN = 1u << MP_BIN,
> > > +	MEM_TYPE_ARRAY = 1u << MP_ARRAY,
> > > +	MEM_TYPE_MAP = 1u << MP_MAP,
> > > +	MEM_TYPE_BOOL = 1u << MP_BOOL,
> > > +	MEM_TYPE_FLOAT = 1u << MP_FLOAT,
> > > +	MEM_TYPE_DOUBLE = 1u << MP_DOUBLE,
> > > +	MEM_TYPE_INVALID = 1u << MP_EXT,
> > > +	MEM_TYPE_FRAME = 1u << (MP_EXT + 1),
> > > +	MEM_TYPE_PTR = 1u << (MP_EXT + 2),
> > > +	MEM_TYPE_AGG = 1u << (MP_EXT + 3),
> > 
> > 4. Why do you stick to mp_types? Why can't have your own
> > bits? Anyway I don't see now any easy conversions between
> > mem_type and mp_type. It is also kind of incorrect because
> > MEM_TYPE_PTR = MP_EXT + 2 is not a pointer in MessagePack,
> > and the same for the other extensions.
> > 
> Fixed.
> 
> > > +};
> > > +
> > > +/** Internally, the vdbe manipulates nearly all SQL values as Mem structures. */
> > >  struct Mem {
> > >  	union MemValue {
> > > -		double r;	/* Real value used when MEM_Real is set in flags */
> > > -		i64 i;		/* Integer value used when MEM_Int is set in flags */
> > > -		uint64_t u;	/* Unsigned integer used when MEM_UInt is set. */
> > > -		bool b;         /* Boolean value used when MEM_Bool is set in flags */
> > > -		int nZero;	/* Used when bit MEM_Zero is set in flags */
> > > -		void *p;	/* Generic pointer */
> > > +		/** Double value when MEM type is MEM_TYPE_DOUBLE. */
> > > +		double r;
> > 
> > 5. When I talked about fixing a comment location, I meant that single
> > comment you needed to change anyway. But ok, lets change all alongside.
> > 
> I decided to revert this change along with removing uTemp and pFiller fields. I
> will do all these changes in another patch, and there will be other changes that
> were mentioned in discussion #6051.
> 
> I still added a comment for new field.
> 
> > > +		/** Negative integer value when MEM type is MEM_TYPE_INT. */
> > > +		i64 i;
> > > +		/** Unsigned integer value when MEM type is MEM_TYPE_UINT. */
> > > +		uint64_t u;
> > > +		/** Boolean value when MEM type is MEM_TYPE_BOOL. */
> > > +		bool b;
> 
> 
> Diff:
> 
> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index 49bf4ae96..f855c111f 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -96,26 +96,31 @@ mem_create(struct Mem *mem)
>  	mem->z = NULL;
>  	mem->zMalloc = NULL;
>  	mem->szMalloc = 0;
> +	mem->uTemp = 0;
>  	mem->db = sql_get();
>  	mem->xDel = NULL;
>  #ifdef SQL_DEBUG
>  	mem->pScopyFrom = NULL;
> +	mem->pFiller = NULL;
>  #endif
>  }
>  
>  static inline void
>  mem_clear(struct Mem *mem)
>  {
> -	if (mem->type == MEM_TYPE_AGG) {
> -		sql_vdbemem_finalize(mem, mem->u.func);
> +	if ((mem->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0 ||
> +	    (mem->flags & MEM_Dyn) != 0) {
> +		if (mem->type == MEM_TYPE_AGG)
> +			sql_vdbemem_finalize(mem, mem->u.func);
>  		assert(mem->type != MEM_TYPE_AGG);
> -	} else if (mem->type == MEM_TYPE_FRAME) {
> -		struct VdbeFrame *frame = mem->u.pFrame;
> -		frame->pParent = frame->v->pDelFrame;
> -		frame->v->pDelFrame = frame;
> -	} else if ((mem->flags & MEM_Dyn) != 0) {
> -		assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
> -		mem->xDel((void *)mem->z);
> +		if ((mem->flags & MEM_Dyn) != 0) {
> +			assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
> +			mem->xDel((void *)mem->z);
> +		} else if (mem->type == MEM_TYPE_FRAME) {
> +			struct VdbeFrame *frame = mem->u.pFrame;
> +			frame->pParent = frame->v->pDelFrame;
> +			frame->v->pDelFrame = frame;
> +		}
>  	}
>  	mem->type = MEM_TYPE_NULL;
>  	mem->flags = 0;
> @@ -271,8 +276,8 @@ mem_set_str0_allocated(struct Mem *mem, char *value)
>  int
>  mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
>  {
> -	if ((mem->type == MEM_TYPE_STR ||
> -	     mem->type == MEM_TYPE_BIN) && mem->z == value) {
> +	if (((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) &&
> +	    mem->z == value) {
>  		/* Own value, but might be ephemeral. Make it own if so. */
>  		if (sqlVdbeMemGrow(mem, len, 1) != 0)
>  			return -1;
> @@ -363,8 +368,8 @@ mem_set_bin_allocated(struct Mem *mem, char *value, uint32_t size)
>  int
>  mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
>  {
> -	if ((mem->type == MEM_TYPE_STR ||
> -	     mem->type == MEM_TYPE_BIN) && mem->z == value) {
> +	if (((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) &&
> +	    mem->z == value) {
>  		/* Own value, but might be ephemeral. Make it own if so. */
>  		if (sqlVdbeMemGrow(mem, size, 1) != 0)
>  			return -1;
> @@ -520,7 +525,7 @@ mem_set_null_clear(struct Mem *mem)
>  static inline int
>  int_to_double(struct Mem *mem)
>  {
> -	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
> +	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
>  	double d;
>  	if (mem->type == MEM_TYPE_UINT)
>  		d = (double)mem->u.u;
> @@ -536,7 +541,7 @@ int_to_double(struct Mem *mem)
>  static inline int
>  int_to_str0(struct Mem *mem)
>  {
> -	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
> +	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
>  	const char *str;
>  	if (mem->type == MEM_TYPE_UINT)
>  		str = tt_sprintf("%llu", mem->u.u);
> @@ -548,7 +553,7 @@ int_to_str0(struct Mem *mem)
>  static inline int
>  int_to_bool(struct Mem *mem)
>  {
> -	assert(mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT);
> +	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
>  	mem->u.b = mem->u.i != 0;
>  	mem->type = MEM_TYPE_BOOL;
>  	assert(mem->flags == 0);
> @@ -635,7 +640,7 @@ bin_to_str0(struct Mem *mem)
>  static inline int
>  bytes_to_int(struct Mem *mem)
>  {
> -	assert(mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN);
> +	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
>  	bool is_neg;
>  	int64_t i;
>  	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
> @@ -647,7 +652,7 @@ bytes_to_int(struct Mem *mem)
>  static inline int
>  bytes_to_uint(struct Mem *mem)
>  {
> -	assert(mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN);
> +	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
>  	bool is_neg;
>  	int64_t i;
>  	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
> @@ -661,7 +666,7 @@ bytes_to_uint(struct Mem *mem)
>  static inline int
>  bytes_to_double(struct Mem *mem)
>  {
> -	assert(mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN);
> +	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
>  	double d;
>  	if (sqlAtoF(mem->z, &d, mem->n) == 0)
>  		return -1;
> @@ -807,14 +812,13 @@ int
>  mem_to_int(struct Mem *mem)
>  {
>  	assert(mem->type < MEM_TYPE_INVALID);
> -	enum mem_type type = mem->type;
> -	if (type == MEM_TYPE_INT || type == MEM_TYPE_UINT)
> +	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  		return 0;
> -	if (type == MEM_TYPE_STR || type == MEM_TYPE_BIN)
> +	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
>  		return bytes_to_int(mem);
> -	if (type == MEM_TYPE_DOUBLE)
> +	if (mem->type == MEM_TYPE_DOUBLE)
>  		return double_to_int(mem);
> -	if (type == MEM_TYPE_BOOL)
> +	if (mem->type == MEM_TYPE_BOOL)
>  		return bool_to_int(mem);
>  	return -1;
>  }
> @@ -823,12 +827,11 @@ int
>  mem_to_int_precise(struct Mem *mem)
>  {
>  	assert(mem->type < MEM_TYPE_INVALID);
> -	enum mem_type type = mem->type;
> -	if (type == MEM_TYPE_INT || type == MEM_TYPE_UINT)
> +	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  		return 0;
> -	if (type == MEM_TYPE_STR)
> +	if (mem->type == MEM_TYPE_STR)
>  		return bytes_to_int(mem);
> -	if (type == MEM_TYPE_DOUBLE)
> +	if (mem->type == MEM_TYPE_DOUBLE)
>  		return double_to_int_precise(mem);
>  	return -1;
>  }
> @@ -837,12 +840,11 @@ int
>  mem_to_double(struct Mem *mem)
>  {
>  	assert(mem->type < MEM_TYPE_INVALID);
> -	enum mem_type type = mem->type;
> -	if (type == MEM_TYPE_DOUBLE)
> +	if (mem->type == MEM_TYPE_DOUBLE)
>  		return 0;
> -	if (type == MEM_TYPE_INT || type == MEM_TYPE_UINT)
> +	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  		return int_to_double(mem);
> -	if (type == MEM_TYPE_STR)
> +	if (mem->type == MEM_TYPE_STR)
>  		return bytes_to_double(mem);
>  	return -1;
>  }
> @@ -855,7 +857,7 @@ mem_to_number(struct Mem *mem)
>  		return 0;
>  	if (mem->type == MEM_TYPE_BOOL)
>  		return bool_to_int(mem);
> -	if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN) {
> +	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) {
>  		if (bytes_to_int(mem) == 0)
>  			return 0;
>  		return bytes_to_double(mem);
> @@ -966,7 +968,7 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
>  	case FIELD_TYPE_NUMBER:
>  		return mem_to_number(mem);
>  	case FIELD_TYPE_SCALAR:
> -		if (mem->type == MEM_TYPE_MAP || mem->type == MEM_TYPE_ARRAY)
> +		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
>  			return -1;
>  		return 0;
>  	default:
> @@ -996,11 +998,11 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
>  	case FIELD_TYPE_DOUBLE:
>  		if (mem->type == MEM_TYPE_DOUBLE)
>  			return 0;
> -		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
> +		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  			return int_to_double(mem);
>  		return -1;
>  	case FIELD_TYPE_INTEGER:
> -		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
> +		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  			return 0;
>  		if (mem->type == MEM_TYPE_DOUBLE)
>  			return double_to_int(mem);
> @@ -1010,7 +1012,8 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_VARBINARY:
> -		if (mem->type != MEM_TYPE_STR && mem_is_bytes(mem))
> +		if ((mem->type & (MEM_TYPE_BIN | MEM_TYPE_MAP |
> +				  MEM_TYPE_ARRAY)) != 0)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_NUMBER:
> @@ -1026,7 +1029,7 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_SCALAR:
> -		if (mem->type == MEM_TYPE_MAP || mem->type == MEM_TYPE_ARRAY)
> +		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
>  			return -1;
>  		return 0;
>  	case FIELD_TYPE_ANY:
> @@ -1052,9 +1055,9 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type)
>  			return bytes_to_uint(mem);
>  		return -1;
>  	case FIELD_TYPE_STRING:
> -		if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN)
> +		if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
>  			return 0;
> -		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
> +		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  			return int_to_str0(mem);
>  		if (mem->type == MEM_TYPE_DOUBLE)
>  			return double_to_str0(mem);
> @@ -1062,13 +1065,13 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type)
>  	case FIELD_TYPE_DOUBLE:
>  		if (mem->type == MEM_TYPE_DOUBLE)
>  			return 0;
> -		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
> +		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  			return int_to_double(mem);
>  		if (mem->type == MEM_TYPE_STR)
>  			return bin_to_str(mem);
>  		return -1;
>  	case FIELD_TYPE_INTEGER:
> -		if (mem->type == MEM_TYPE_INT || mem->type == MEM_TYPE_UINT)
> +		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  			return 0;
>  		if (mem->type == MEM_TYPE_STR)
>  			return bytes_to_int(mem);
> @@ -1098,7 +1101,7 @@ mem_cast_implicit_old(struct Mem *mem, enum field_type type)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_SCALAR:
> -		if (mem->type == MEM_TYPE_MAP || mem->type == MEM_TYPE_ARRAY)
> +		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
>  			return -1;
>  		return 0;
>  	default:
> @@ -1120,7 +1123,7 @@ mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
>  		*is_neg = false;
>  		return 0;
>  	}
> -	if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN)
> +	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
>  		return sql_atoi64(mem->z, i, is_neg, mem->n);
>  	if (mem->type == MEM_TYPE_DOUBLE) {
>  		double d = mem->u.r;
> @@ -1148,7 +1151,7 @@ mem_get_uint(const struct Mem *mem, uint64_t *u)
>  		*u = mem->u.u;
>  		return 0;
>  	}
> -	if (mem->type == MEM_TYPE_STR || mem->type == MEM_TYPE_BIN) {
> +	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) {
>  		bool is_neg;
>  		if (sql_atoi64(mem->z, (int64_t *)u, &is_neg, mem->n) != 0 ||
>  		    is_neg)
> @@ -1305,7 +1308,7 @@ try_return_null(const struct Mem *a, const struct Mem *b, struct Mem *result,
>  {
>  	mem_clear(result);
>  	result->field_type = type;
> -	return a->type == MEM_TYPE_NULL || b->type == MEM_TYPE_NULL;
> +	return ((a->type | b->type) & MEM_TYPE_NULL) != 0;
>  }
>  
>  int
> @@ -1316,7 +1319,7 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
>  		if (try_return_null(a, b, result, FIELD_TYPE_STRING))
>  			return 0;
>  	} else {
> -		if (a->type == MEM_TYPE_NULL || b->type == MEM_TYPE_NULL) {
> +		if (((a->type | b->type) & MEM_TYPE_NULL) != 0) {
>  			mem_clear(a);
>  			result->field_type = FIELD_TYPE_STRING;
>  			return 0;
> @@ -1324,18 +1327,19 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
>  	}
>  
>  	/* Concatenation operation can be applied only to strings and blobs. */
> -	if (!mem_is_bytes(b)) {
> +	if (((b->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)) {
>  		diag_set(ClientError, ER_INCONSISTENT_TYPES,
>  			 "text or varbinary", mem_type_to_str(b));
>  		return -1;
>  	}
> -	if (!mem_is_bytes(a)) {
> +	if (((a->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)) {
>  		diag_set(ClientError, ER_INCONSISTENT_TYPES,
>  			 "text or varbinary", mem_type_to_str(a));
>  		return -1;
>  	}
>  
> -	if (b->type != a->type && ((b->type | a->type) & MEM_TYPE_STR) != 0) {
> +	/* Moreover, both operands must be of the same type. */
> +	if (b->type != a->type) {
>  		diag_set(ClientError, ER_INCONSISTENT_TYPES,
>  			 mem_type_to_str(a), mem_type_to_str(b));
>  		return -1;
> @@ -1393,7 +1397,7 @@ get_number(const struct Mem *mem, struct sql_num *number)
>  		number->is_neg = false;
>  		return 0;
>  	}
> -	if (mem->type != MEM_TYPE_STR && mem->type != MEM_TYPE_BIN)
> +	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)
>  		return -1;
>  	if (sql_atoi64(mem->z, &number->i, &number->is_neg, mem->n) == 0) {
>  		number->type = number->is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
> @@ -1428,16 +1432,16 @@ arithmetic_prepare(const struct Mem *left, const struct Mem *right,
>  		return -1;
>  	}
>  	assert(a->type != 0 && b->type != 0);
> -	if (a->type == MEM_TYPE_DOUBLE && b->type != MEM_TYPE_DOUBLE) {
> +	if (a->type == b->type || ((a->type | b->type) & MEM_TYPE_DOUBLE) == 0)
> +		return 0;
> +	if (a->type == MEM_TYPE_DOUBLE) {
>  		b->d = b->type == MEM_TYPE_INT ? (double)b->i : (double)b->u;
>  		b->type = MEM_TYPE_DOUBLE;
>  		return 0;
>  	}
> -	if (a->type != MEM_TYPE_DOUBLE && b->type == MEM_TYPE_DOUBLE) {
> -		a->d = a->type == MEM_TYPE_INT ? (double)a->i : (double)a->u;
> -		a->type = MEM_TYPE_DOUBLE;
> -		return 0;
> -	}
> +	assert(b->type == MEM_TYPE_DOUBLE);
> +	a->d = a->type == MEM_TYPE_INT ? (double)a->i : (double)a->u;
> +	a->type = MEM_TYPE_DOUBLE;
>  	return 0;
>  }
>  
> @@ -1735,7 +1739,7 @@ mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
>  int
>  mem_cmp_bin(const struct Mem *a, const struct Mem *b, int *result)
>  {
> -	if (!mem_is_bin(a) || !mem_is_bin(b))
> +	if ((a->type & b->type & MEM_TYPE_BIN) == 0)
>  		return -1;
>  	int an = a->n;
>  	int bn = b->n;
> @@ -2025,7 +2029,7 @@ sqlVdbeCheckMemInvariants(Mem * p)
>  	 *   (3) An ephemeral string or blob
>  	 *   (4) A static string or blob
>  	 */
> -	if ((p->type == MEM_TYPE_STR || p->type == MEM_TYPE_BIN) && p->n > 0) {
> +	if ((p->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0 && p->n > 0) {
>  		assert(((p->szMalloc > 0 && p->z == p->zMalloc) ? 1 : 0) +
>  		       ((p->flags & MEM_Dyn) != 0 ? 1 : 0) +
>  		       ((p->flags & MEM_Ephem) != 0 ? 1 : 0) +
> @@ -2143,7 +2147,7 @@ memTracePrint(Mem *p)
>  		char zBuf[200];
>  		sqlVdbeMemPrettyPrint(p, zBuf);
>  		printf(" %s", zBuf);
> -		if (p->type == MEM_TYPE_MAP || p->type == MEM_TYPE_ARRAY)
> +		if ((p->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
>  			printf(" subtype=0x%02x", SQL_SUBTYPE_MSGPACK);
>  		return;
>  	}
> @@ -2338,11 +2342,11 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
>  	/* If one value is NULL, it is less than the other. If both values
>  	 * are NULL, return 0.
>  	 */
> -	if (type1 == MEM_TYPE_NULL || type2 == MEM_TYPE_NULL)
> +	if (((type1 | type2) & MEM_TYPE_NULL) != 0)
>  		return (int)(type2 == MEM_TYPE_NULL) -
>  		       (int)(type1 == MEM_TYPE_NULL);
>  
> -	if (type1 == MEM_TYPE_BOOL || type2 == MEM_TYPE_BOOL) {
> +	if (((type1 | type2) & MEM_TYPE_BOOL) != 0) {
>  		if (type1 == MEM_TYPE_BOOL && type2 == MEM_TYPE_BOOL) {
>  			if (pMem1->u.b == pMem2->u.b)
>  				return 0;
> @@ -2357,7 +2361,8 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
>  
>  	/* At least one of the two values is a number
>  	 */
> -	if (mem_is_num(pMem1) || mem_is_num(pMem2)) {
> +	if (((type1 | type2) &
> +	     (MEM_TYPE_INT | MEM_TYPE_UINT | MEM_TYPE_DOUBLE)) != 0) {
>  		if (!mem_is_num(pMem1))
>  			return +1;
>  		if (!mem_is_num(pMem2))
> @@ -2369,7 +2374,7 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
>  	/* If one value is a string and the other is a blob, the string is less.
>  	 * If both are strings, compare using the collating functions.
>  	 */
> -	if (type1 == MEM_TYPE_STR || type2 == MEM_TYPE_STR) {
> +	if (((type1 | type2) & MEM_TYPE_STR) != 0) {
>  		if (type1 != MEM_TYPE_STR) {
>  			return 1;
>  		}
> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index 59f006dd6..29d373cfd 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -38,73 +38,65 @@ struct mpstream;
>  struct VdbeFrame;
>  
>  enum mem_type {
> -	MEM_TYPE_NULL = 1u << MP_NIL,
> -	MEM_TYPE_UINT = 1u << MP_UINT,
> -	MEM_TYPE_INT = 1u << MP_INT,
> -	MEM_TYPE_STR = 1u << MP_STR,
> -	MEM_TYPE_BIN = 1u << MP_BIN,
> -	MEM_TYPE_ARRAY = 1u << MP_ARRAY,
> -	MEM_TYPE_MAP = 1u << MP_MAP,
> -	MEM_TYPE_BOOL = 1u << MP_BOOL,
> -	MEM_TYPE_FLOAT = 1u << MP_FLOAT,
> -	MEM_TYPE_DOUBLE = 1u << MP_DOUBLE,
> -	MEM_TYPE_INVALID = 1u << MP_EXT,
> -	MEM_TYPE_FRAME = 1u << (MP_EXT + 1),
> -	MEM_TYPE_PTR = 1u << (MP_EXT + 2),
> -	MEM_TYPE_AGG = 1u << (MP_EXT + 3),
> +	MEM_TYPE_NULL		= 1,
> +	MEM_TYPE_UINT		= 1 << 1,
> +	MEM_TYPE_INT		= 1 << 2,
> +	MEM_TYPE_STR		= 1 << 3,
> +	MEM_TYPE_BIN		= 1 << 4,
> +	MEM_TYPE_ARRAY		= 1 << 5,
> +	MEM_TYPE_MAP		= 1 << 6,
> +	MEM_TYPE_BOOL		= 1 << 7,
> +	MEM_TYPE_FLOAT		= 1 << 8,
> +	MEM_TYPE_DOUBLE		= 1 << 9,
> +	MEM_TYPE_INVALID	= 1 << 10,
> +	MEM_TYPE_FRAME		= 1 << 11,
> +	MEM_TYPE_PTR		= 1 << 12,
> +	MEM_TYPE_AGG		= 1 << 13,
>  };
>  
> -/** Internally, the vdbe manipulates nearly all SQL values as Mem structures. */
> +/*
> + * Internally, the vdbe manipulates nearly all SQL values as Mem
> + * structures. Each Mem struct may cache multiple representations (string,
> + * integer etc.) of the same value.
> + */
>  struct Mem {
>  	union MemValue {
> -		/** Double value when MEM type is MEM_TYPE_DOUBLE. */
> -		double r;
> -		/** Negative integer value when MEM type is MEM_TYPE_INT. */
> -		i64 i;
> -		/** Unsigned integer value when MEM type is MEM_TYPE_UINT. */
> -		uint64_t u;
> -		/** Boolean value when MEM type is MEM_TYPE_BOOL. */
> -		bool b;
> +		double r;	/* Real value used when MEM_Real is set in flags */
> +		i64 i;		/* Integer value used when MEM_Int is set in flags */
> +		uint64_t u;	/* Unsigned integer used when MEM_UInt is set. */
> +		bool b;         /* Boolean value used when MEM_Bool is set in flags */
> +		int nZero;	/* Used when bit MEM_Zero is set in flags */
> +		void *p;	/* Generic pointer */
>  		/**
> -		 * Number of zeroes when MEM type is MEM_TYPE_BIN and MEM_Zero
> -		 * flag is set.
> +		 * A pointer to function implementation.
> +		 * Used only when flags==MEM_Agg.
>  		 */
> -		int nZero;
> -		/** Generic pointer when MEM type is MEM_TYPE_PTR. */
> -		void *p;
> -		/** Pointer to function when MEM type is MEM_TYPE_AGG. */
>  		struct func *func;
> -		/** Pointer to frame when MEM type is MEM_TYPE_FRAME. */
> -		struct VdbeFrame *pFrame;
> +		struct VdbeFrame *pFrame;	/* Used when flags==MEM_Frame */
>  	} u;
>  	/** Type of the value this MEM contains. */
>  	enum mem_type type;
> -	/** Additional information for MEM types. */
> -	u32 flags;
> +	u32 flags;		/* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
> +	/** Subtype for this value. */
> +	enum sql_subtype subtype;
>  	/**
> -	 * Field type of the value this MEM contains. Should be field_type_MAX
> -	 * or field type compatible with MEM type.
> +	 * If value is fetched from tuple, then this property
> +	 * contains type of corresponding space's field. If it's
> +	 * value field_type_MAX then we can rely on on format
> +	 * (msgpack) type which is represented by 'flags'.
>  	 */
>  	enum field_type field_type;
> -	/**
> -	 * The size in bytes for a STRING, VARBINARY, MAP, or ARRAY value. For a
> -	 * STRING, if MEM_Term is set, the actual size is n + 1, since there is
> -	 * trailing '\0'.
> -	 */
> -	int n;
> -	/** STRING, VARBINARY, MAP, or ARRAY if set to the appropriate type. */
> -	char *z;
> -	/** Memory, allocated by MEM. */
> -	char *zMalloc;
> -	/** Size of memory, allocated by MEM. */
> -	int szMalloc;
> -	/** Database that contains this MEM. */
> -	struct sql *db;
> -	/** Destructor in case MEM_Dyn is set. */
> -	void (*xDel) (void *);
> +	int n;			/* size (in bytes) of string value, excluding trailing '\0' */
> +	char *z;		/* String or BLOB value */
> +	/* ShallowCopy only needs to copy the information above */
> +	char *zMalloc;		/* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */
> +	int szMalloc;		/* Size of the zMalloc allocation */
> +	u32 uTemp;		/* Transient storage for serial_type in OP_MakeRecord */
> +	sql *db;		/* The associated database connection */
> +	void (*xDel) (void *);	/* Destructor for Mem.z - only valid if MEM_Dyn */
>  #ifdef SQL_DEBUG
> -	/** This Mem is a shallow copy of pScopyFrom. */
> -	struct Mem *pScopyFrom;
> +	Mem *pScopyFrom;	/* This Mem is a shallow copy of pScopyFrom */
> +	void *pFiller;		/* So that sizeof(Mem) is a multiple of 8 */
>  #endif
>  };
>  
> @@ -263,7 +255,7 @@ mem_is_same_type(const struct Mem *mem1, const struct Mem *mem2)
>  static inline bool
>  mem_is_any_null(const struct Mem *mem1, const struct Mem *mem2)
>  {
> -	return mem1->type == MEM_TYPE_NULL || mem2->type == MEM_TYPE_NULL;
> +	return ((mem1->type| mem2->type) & MEM_TYPE_NULL) != 0;
>  }
>  
>  /**
> @@ -950,10 +942,8 @@ int sqlVdbeMemTooBig(Mem *);
>  /* Return TRUE if Mem X contains dynamically allocated content - anything
>   * that needs to be deallocated to avoid a leak.
>   */
> -#define VdbeMemDynamic(X) ((X)->type == MEM_TYPE_AGG || \
> -			   (X)->type == MEM_TYPE_FRAME || \
> -			   ((X)->flags & MEM_Dyn) != 0)
> -
> +#define VdbeMemDynamic(X) (((X)->flags & MEM_Dyn) != 0 ||\
> +			   ((X)->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0)
>  
>  int sqlMemCompare(const Mem *, const Mem *, const struct coll *);
>  
> 
> 
> 
> 
> New patch:
> 
> 
> commit a17b49b00a6241d5c240da4ff9ec24379b7e0431
> Author: Mergen Imeev <imeevma@gmail.com>
> Date:   Fri Apr 16 18:06:31 2021 +0300
> 
>     sql: replace MEM-type flags by enum mem_type
>     
>     This patch moves MEM types from the 'u32 flags' field to the new
>     'enum mem_type type' field. Now, we can be sure that only one type is
>     set for MEM. In addition, it is now easier to distinguish MAP and ARRAY
>     from VARBINARY, and this makes it easier to add extension types - UUID
>     and DECIMAL.
>     
>     Closes #4906
> 
> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index b6ff6397f..f855c111f 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -60,26 +60,26 @@ const char *
>  mem_str(const struct Mem *mem)
>  {
>  	char buf[BUF_SIZE];
> -	switch (mem->flags & MEM_PURE_TYPE_MASK) {
> -	case MEM_Null:
> +	switch (mem->type) {
> +	case MEM_TYPE_NULL:
>  		return "NULL";
> -	case MEM_Str:
> +	case MEM_TYPE_STR:
>  		if ((mem->flags & MEM_Term) != 0)
>  			return mem->z;
>  		return tt_cstr(mem->z, mem->n);
> -	case MEM_Int:
> +	case MEM_TYPE_INT:
>  		return tt_sprintf("%lld", mem->u.i);
> -	case MEM_UInt:
> +	case MEM_TYPE_UINT:
>  		return tt_sprintf("%llu", mem->u.u);
> -	case MEM_Real:
> +	case MEM_TYPE_DOUBLE:
>  		sql_snprintf(BUF_SIZE, &buf[0], "%!.15g", mem->u.r);
>  		return tt_sprintf("%s", buf);
> -	case MEM_Blob:
> -		if ((mem->flags & MEM_Subtype) == 0)
> -			return "varbinary";
> -		assert(mem->subtype == SQL_SUBTYPE_MSGPACK);
> +	case MEM_TYPE_BIN:
> +		return "varbinary";
> +	case MEM_TYPE_MAP:
> +	case MEM_TYPE_ARRAY:
>  		return mp_str(mem->z);
> -	case MEM_Bool:
> +	case MEM_TYPE_BOOL:
>  		return mem->u.b ? "TRUE" : "FALSE";
>  	default:
>  		return "unknown";
> @@ -89,8 +89,8 @@ mem_str(const struct Mem *mem)
>  void
>  mem_create(struct Mem *mem)
>  {
> -	mem->flags = MEM_Null;
> -	mem->subtype = SQL_SUBTYPE_NO;
> +	mem->type = MEM_TYPE_NULL;
> +	mem->flags = 0;
>  	mem->field_type = field_type_MAX;
>  	mem->n = 0;
>  	mem->z = NULL;
> @@ -108,20 +108,22 @@ mem_create(struct Mem *mem)
>  static inline void
>  mem_clear(struct Mem *mem)
>  {
> -	if ((mem->flags & (MEM_Agg | MEM_Dyn | MEM_Frame)) != 0) {
> -		if ((mem->flags & MEM_Agg) != 0)
> +	if ((mem->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0 ||
> +	    (mem->flags & MEM_Dyn) != 0) {
> +		if (mem->type == MEM_TYPE_AGG)
>  			sql_vdbemem_finalize(mem, mem->u.func);
> -		assert((mem->flags & MEM_Agg) == 0);
> +		assert(mem->type != MEM_TYPE_AGG);
>  		if ((mem->flags & MEM_Dyn) != 0) {
>  			assert(mem->xDel != SQL_DYNAMIC && mem->xDel != NULL);
>  			mem->xDel((void *)mem->z);
> -		} else if ((mem->flags & MEM_Frame) != 0) {
> +		} else if (mem->type == MEM_TYPE_FRAME) {
>  			struct VdbeFrame *frame = mem->u.pFrame;
>  			frame->pParent = frame->v->pDelFrame;
>  			frame->v->pDelFrame = frame;
>  		}
>  	}
> -	mem->flags = MEM_Null;
> +	mem->type = MEM_TYPE_NULL;
> +	mem->flags = 0;
>  	mem->field_type = field_type_MAX;
>  }
>  
> @@ -149,7 +151,8 @@ mem_set_int(struct Mem *mem, int64_t value, bool is_neg)
>  {
>  	mem_clear(mem);
>  	mem->u.i = value;
> -	mem->flags = is_neg ? MEM_Int : MEM_UInt;
> +	mem->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(mem->flags == 0);
>  	mem->field_type = FIELD_TYPE_INTEGER;
>  }
>  
> @@ -158,7 +161,8 @@ mem_set_uint(struct Mem *mem, uint64_t value)
>  {
>  	mem_clear(mem);
>  	mem->u.u = value;
> -	mem->flags = MEM_UInt;
> +	mem->type = MEM_TYPE_UINT;
> +	assert(mem->flags == 0);
>  	mem->field_type = FIELD_TYPE_UNSIGNED;
>  }
>  
> @@ -167,7 +171,8 @@ mem_set_bool(struct Mem *mem, bool value)
>  {
>  	mem_clear(mem);
>  	mem->u.b = value;
> -	mem->flags = MEM_Bool;
> +	mem->type = MEM_TYPE_BOOL;
> +	assert(mem->flags == 0);
>  	mem->field_type = FIELD_TYPE_BOOLEAN;
>  }
>  
> @@ -176,10 +181,11 @@ mem_set_double(struct Mem *mem, double value)
>  {
>  	mem_clear(mem);
>  	mem->field_type = FIELD_TYPE_DOUBLE;
> +	assert(mem->flags == 0);
>  	if (sqlIsNaN(value))
>  		return;
>  	mem->u.r = value;
> -	mem->flags = MEM_Real;
> +	mem->type = MEM_TYPE_DOUBLE;
>  }
>  
>  static inline void
> @@ -189,7 +195,8 @@ set_str_const(struct Mem *mem, char *value, uint32_t len, int alloc_type)
>  	mem_clear(mem);
>  	mem->z = value;
>  	mem->n = len;
> -	mem->flags = MEM_Str | alloc_type;
> +	mem->type = MEM_TYPE_STR;
> +	mem->flags = alloc_type;
>  	mem->field_type = FIELD_TYPE_STRING;
>  }
>  
> @@ -202,7 +209,8 @@ set_str_dynamic(struct Mem *mem, char *value, uint32_t len, int alloc_type)
>  	mem_destroy(mem);
>  	mem->z = value;
>  	mem->n = len;
> -	mem->flags = MEM_Str | alloc_type;
> +	mem->type = MEM_TYPE_STR;
> +	mem->flags = alloc_type;
>  	mem->field_type = FIELD_TYPE_STRING;
>  	if (alloc_type == MEM_Dyn) {
>  		mem->xDel = sql_free;
> @@ -268,11 +276,13 @@ mem_set_str0_allocated(struct Mem *mem, char *value)
>  int
>  mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
>  {
> -	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0 && mem->z == value) {
> +	if (((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) &&
> +	    mem->z == value) {
>  		/* Own value, but might be ephemeral. Make it own if so. */
>  		if (sqlVdbeMemGrow(mem, len, 1) != 0)
>  			return -1;
> -		mem->flags = MEM_Str;
> +		mem->type = MEM_TYPE_STR;
> +		mem->flags = 0;
>  		mem->field_type = FIELD_TYPE_STRING;
>  		return 0;
>  	}
> @@ -281,7 +291,8 @@ mem_copy_str(struct Mem *mem, const char *value, uint32_t len)
>  		return -1;
>  	memcpy(mem->z, value, len);
>  	mem->n = len;
> -	mem->flags = MEM_Str;
> +	mem->type = MEM_TYPE_STR;
> +	assert(mem->flags == 0);
>  	mem->field_type = FIELD_TYPE_STRING;
>  	return 0;
>  }
> @@ -304,7 +315,8 @@ set_bin_const(struct Mem *mem, char *value, uint32_t size, int alloc_type)
>  	mem_clear(mem);
>  	mem->z = value;
>  	mem->n = size;
> -	mem->flags = MEM_Blob | alloc_type;
> +	mem->type = MEM_TYPE_BIN;
> +	mem->flags = alloc_type;
>  	mem->field_type = FIELD_TYPE_VARBINARY;
>  }
>  
> @@ -317,7 +329,8 @@ set_bin_dynamic(struct Mem *mem, char *value, uint32_t size, int alloc_type)
>  	mem_destroy(mem);
>  	mem->z = value;
>  	mem->n = size;
> -	mem->flags = MEM_Blob | alloc_type;
> +	mem->type = MEM_TYPE_BIN;
> +	mem->flags = alloc_type;
>  	mem->field_type = FIELD_TYPE_VARBINARY;
>  	if (alloc_type == MEM_Dyn) {
>  		mem->xDel = sql_free;
> @@ -355,11 +368,13 @@ mem_set_bin_allocated(struct Mem *mem, char *value, uint32_t size)
>  int
>  mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
>  {
> -	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0 && mem->z == value) {
> +	if (((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) &&
> +	    mem->z == value) {
>  		/* Own value, but might be ephemeral. Make it own if so. */
>  		if (sqlVdbeMemGrow(mem, size, 1) != 0)
>  			return -1;
> -		mem->flags = MEM_Blob;
> +		mem->type = MEM_TYPE_BIN;
> +		mem->flags = 0;
>  		mem->field_type = FIELD_TYPE_VARBINARY;
>  		return 0;
>  	}
> @@ -368,7 +383,8 @@ mem_copy_bin(struct Mem *mem, const char *value, uint32_t size)
>  		return -1;
>  	memcpy(mem->z, value, size);
>  	mem->n = size;
> -	mem->flags = MEM_Blob;
> +	mem->type = MEM_TYPE_BIN;
> +	assert(mem->flags == 0);
>  	mem->field_type = FIELD_TYPE_VARBINARY;
>  	return 0;
>  }
> @@ -382,7 +398,8 @@ mem_set_zerobin(struct Mem *mem, int n)
>  	mem->u.nZero = n;
>  	mem->z = NULL;
>  	mem->n = 0;
> -	mem->flags = MEM_Blob | MEM_Zero;
> +	mem->type = MEM_TYPE_BIN;
> +	mem->flags = MEM_Zero;
>  	mem->field_type = FIELD_TYPE_VARBINARY;
>  }
>  
> @@ -390,12 +407,12 @@ static inline void
>  set_msgpack_value(struct Mem *mem, char *value, uint32_t size, int alloc_type,
>  		  enum field_type type)
>  {
> +	assert(type == FIELD_TYPE_MAP || type == FIELD_TYPE_ARRAY);
>  	if (alloc_type == MEM_Ephem || alloc_type == MEM_Static)
>  		set_bin_const(mem, value, size, alloc_type);
>  	else
>  		set_bin_dynamic(mem, value, size, alloc_type);
> -	mem->flags |= MEM_Subtype;
> -	mem->subtype = SQL_SUBTYPE_MSGPACK;
> +	mem->type = type == FIELD_TYPE_MAP ? MEM_TYPE_MAP : MEM_TYPE_ARRAY;
>  	mem->field_type = type;
>  }
>  
> @@ -459,14 +476,16 @@ void
>  mem_set_invalid(struct Mem *mem)
>  {
>  	mem_clear(mem);
> -	mem->flags = MEM_Undefined;
> +	mem->type = MEM_TYPE_INVALID;
> +	assert(mem->flags == 0);
>  }
>  
>  void
>  mem_set_ptr(struct Mem *mem, void *ptr)
>  {
>  	mem_clear(mem);
> -	mem->flags = MEM_Ptr;
> +	mem->type = MEM_TYPE_PTR;
> +	assert(mem->flags == 0);
>  	mem->u.p = ptr;
>  }
>  
> @@ -474,7 +493,8 @@ void
>  mem_set_frame(struct Mem *mem, struct VdbeFrame *frame)
>  {
>  	mem_clear(mem);
> -	mem->flags = MEM_Frame;
> +	mem->type = MEM_TYPE_FRAME;
> +	assert(mem->flags == 0);
>  	mem->u.pFrame = frame;
>  }
>  
> @@ -488,7 +508,8 @@ mem_set_agg(struct Mem *mem, struct func *func, int size)
>  		return -1;
>  	memset(mem->z, 0, size);
>  	mem->n = size;
> -	mem->flags = MEM_Agg;
> +	mem->type = MEM_TYPE_AGG;
> +	assert(mem->flags == 0);
>  	mem->u.func = func;
>  	mem->field_type = field_type_MAX;
>  	return 0;
> @@ -498,19 +519,21 @@ void
>  mem_set_null_clear(struct Mem *mem)
>  {
>  	mem_clear(mem);
> -	mem->flags = MEM_Null | MEM_Cleared;
> +	mem->flags = MEM_Cleared;
>  }
>  
>  static inline int
>  int_to_double(struct Mem *mem)
>  {
> +	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
>  	double d;
> -	if ((mem->flags & MEM_UInt) != 0)
> +	if (mem->type == MEM_TYPE_UINT)
>  		d = (double)mem->u.u;
>  	else
>  		d = (double)mem->u.i;
>  	mem->u.r = d;
> -	mem->flags = MEM_Real;
> +	mem->type = MEM_TYPE_DOUBLE;
> +	assert(mem->flags == 0);
>  	mem->field_type = FIELD_TYPE_DOUBLE;
>  	return 0;
>  }
> @@ -518,8 +541,9 @@ int_to_double(struct Mem *mem)
>  static inline int
>  int_to_str0(struct Mem *mem)
>  {
> +	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
>  	const char *str;
> -	if ((mem->flags & MEM_UInt) != 0)
> +	if (mem->type == MEM_TYPE_UINT)
>  		str = tt_sprintf("%llu", mem->u.u);
>  	else
>  		str = tt_sprintf("%lld", mem->u.i);
> @@ -529,8 +553,10 @@ int_to_str0(struct Mem *mem)
>  static inline int
>  int_to_bool(struct Mem *mem)
>  {
> +	assert((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0);
>  	mem->u.b = mem->u.i != 0;
> -	mem->flags = MEM_Bool;
> +	mem->type = MEM_TYPE_BOOL;
> +	assert(mem->flags == 0);
>  	mem->field_type = FIELD_TYPE_BOOLEAN;
>  	return 0;
>  }
> @@ -538,7 +564,7 @@ int_to_bool(struct Mem *mem)
>  static inline int
>  str_to_str0(struct Mem *mem)
>  {
> -	assert((mem->flags | MEM_Str) != 0);
> +	assert(mem->type == MEM_TYPE_STR);
>  	if (sqlVdbeMemGrow(mem, mem->n + 1, 1) != 0)
>  		return -1;
>  	mem->z[mem->n] = '\0';
> @@ -550,8 +576,9 @@ str_to_str0(struct Mem *mem)
>  static inline int
>  str_to_bin(struct Mem *mem)
>  {
> -	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
> -		     MEM_Blob;
> +	assert(mem->type == MEM_TYPE_STR);
> +	mem->type = MEM_TYPE_BIN;
> +	mem->flags &= ~MEM_Term;
>  	mem->field_type = FIELD_TYPE_VARBINARY;
>  	return 0;
>  }
> @@ -559,6 +586,7 @@ str_to_bin(struct Mem *mem)
>  static inline int
>  str_to_bool(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_STR);
>  	char *str = mem->z;
>  	bool b;
>  	const char *str_true = "TRUE";
> @@ -586,10 +614,10 @@ str_to_bool(struct Mem *mem)
>  static inline int
>  bin_to_str(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_BIN);
>  	if (ExpandBlob(mem) != 0)
>  		return -1;
> -	mem->flags = (mem->flags & (MEM_Dyn | MEM_Static | MEM_Ephem)) |
> -		      MEM_Str;
> +	mem->type = MEM_TYPE_STR;
>  	mem->field_type = FIELD_TYPE_STRING;
>  	return 0;
>  }
> @@ -597,12 +625,14 @@ bin_to_str(struct Mem *mem)
>  static inline int
>  bin_to_str0(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_BIN);
>  	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->type = MEM_TYPE_STR;
> +	mem->flags = MEM_Term;
>  	mem->field_type = FIELD_TYPE_STRING;
>  	return 0;
>  }
> @@ -610,6 +640,7 @@ bin_to_str0(struct Mem *mem)
>  static inline int
>  bytes_to_int(struct Mem *mem)
>  {
> +	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
>  	bool is_neg;
>  	int64_t i;
>  	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
> @@ -621,6 +652,7 @@ bytes_to_int(struct Mem *mem)
>  static inline int
>  bytes_to_uint(struct Mem *mem)
>  {
> +	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
>  	bool is_neg;
>  	int64_t i;
>  	if (sql_atoi64(mem->z, &i, &is_neg, mem->n) != 0)
> @@ -634,6 +666,7 @@ bytes_to_uint(struct Mem *mem)
>  static inline int
>  bytes_to_double(struct Mem *mem)
>  {
> +	assert((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0);
>  	double d;
>  	if (sqlAtoF(mem->z, &d, mem->n) == 0)
>  		return -1;
> @@ -644,16 +677,19 @@ bytes_to_double(struct Mem *mem)
>  static inline int
>  double_to_int(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_DOUBLE);
>  	double d = mem->u.r;
>  	if (d < 0 && d >= (double)INT64_MIN) {
>  		mem->u.i = (int64_t)d;
> -		mem->flags = MEM_Int;
> +		mem->type = MEM_TYPE_INT;
> +		assert(mem->flags == 0);
>  		mem->field_type = FIELD_TYPE_INTEGER;
>  		return 0;
>  	}
>  	if (d >= 0 && d < (double)UINT64_MAX) {
>  		mem->u.u = (uint64_t)d;
> -		mem->flags = MEM_UInt;
> +		mem->type = MEM_TYPE_UINT;
> +		assert(mem->flags == 0);
>  		mem->field_type = FIELD_TYPE_UNSIGNED;
>  		return 0;
>  	}
> @@ -663,16 +699,19 @@ double_to_int(struct Mem *mem)
>  static inline int
>  double_to_int_precise(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_DOUBLE);
>  	double d = mem->u.r;
>  	if (d < 0 && d >= (double)INT64_MIN && (double)(int64_t)d == d) {
>  		mem->u.i = (int64_t)d;
> -		mem->flags = MEM_Int;
> +		mem->type = MEM_TYPE_INT;
> +		assert(mem->flags == 0);
>  		mem->field_type = FIELD_TYPE_INTEGER;
>  		return 0;
>  	}
>  	if (d >= 0 && d < (double)UINT64_MAX && (double)(uint64_t)d == d) {
>  		mem->u.u = (uint64_t)d;
> -		mem->flags = MEM_UInt;
> +		mem->type = MEM_TYPE_UINT;
> +		assert(mem->flags == 0);
>  		mem->field_type = FIELD_TYPE_UNSIGNED;
>  		return 0;
>  	}
> @@ -682,10 +721,12 @@ double_to_int_precise(struct Mem *mem)
>  static inline int
>  double_to_uint(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_DOUBLE);
>  	double d = mem->u.r;
>  	if (d >= 0 && d < (double)UINT64_MAX) {
>  		mem->u.u = (uint64_t)d;
> -		mem->flags = MEM_UInt;
> +		mem->type = MEM_TYPE_UINT;
> +		assert(mem->flags == 0);
>  		mem->field_type = FIELD_TYPE_UNSIGNED;
>  		return 0;
>  	}
> @@ -695,10 +736,12 @@ double_to_uint(struct Mem *mem)
>  static inline int
>  double_to_uint_precise(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_DOUBLE);
>  	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->type = MEM_TYPE_UINT;
> +		assert(mem->flags == 0);
>  		mem->field_type = FIELD_TYPE_UNSIGNED;
>  		return 0;
>  	}
> @@ -708,11 +751,13 @@ double_to_uint_precise(struct Mem *mem)
>  static inline int
>  double_to_str0(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_DOUBLE);
>  	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->type = MEM_TYPE_STR;
> +	mem->flags = MEM_Term;
>  	mem->field_type = FIELD_TYPE_STRING;
>  	return 0;
>  }
> @@ -720,8 +765,10 @@ double_to_str0(struct Mem *mem)
>  static inline int
>  double_to_bool(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_DOUBLE);
>  	mem->u.b = mem->u.r != 0.;
> -	mem->flags = MEM_Bool;
> +	mem->type = MEM_TYPE_BOOL;
> +	assert(mem->flags == 0);
>  	mem->field_type = FIELD_TYPE_BOOLEAN;
>  	return 0;
>  }
> @@ -729,8 +776,10 @@ double_to_bool(struct Mem *mem)
>  static inline int
>  bool_to_int(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_BOOL);
>  	mem->u.u = (uint64_t)mem->u.b;
> -	mem->flags = MEM_UInt;
> +	mem->type = MEM_TYPE_UINT;
> +	assert(mem->flags == 0);
>  	mem->field_type = FIELD_TYPE_UNSIGNED;
>  	return 0;
>  }
> @@ -738,6 +787,7 @@ bool_to_int(struct Mem *mem)
>  static inline int
>  bool_to_str0(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_BOOL);
>  	const char *str = mem->u.b ? "TRUE" : "FALSE";
>  	return mem_copy_str0(mem, str);
>  }
> @@ -745,6 +795,7 @@ bool_to_str0(struct Mem *mem)
>  static inline int
>  array_to_str0(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_ARRAY);
>  	const char *str = mp_str(mem->z);
>  	return mem_copy_str0(mem, str);
>  }
> @@ -752,6 +803,7 @@ array_to_str0(struct Mem *mem)
>  static inline int
>  map_to_str0(struct Mem *mem)
>  {
> +	assert(mem->type == MEM_TYPE_MAP);
>  	const char *str = mp_str(mem->z);
>  	return mem_copy_str0(mem, str);
>  }
> @@ -759,14 +811,14 @@ map_to_str0(struct Mem *mem)
>  int
>  mem_to_int(struct Mem *mem)
>  {
> -	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
> -	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +	assert(mem->type < MEM_TYPE_INVALID);
> +	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  		return 0;
> -	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
> +	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
>  		return bytes_to_int(mem);
> -	if ((mem->flags & MEM_Real) != 0)
> +	if (mem->type == MEM_TYPE_DOUBLE)
>  		return double_to_int(mem);
> -	if ((mem->flags & MEM_Bool) != 0)
> +	if (mem->type == MEM_TYPE_BOOL)
>  		return bool_to_int(mem);
>  	return -1;
>  }
> @@ -774,12 +826,12 @@ mem_to_int(struct Mem *mem)
>  int
>  mem_to_int_precise(struct Mem *mem)
>  {
> -	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
> -	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +	assert(mem->type < MEM_TYPE_INVALID);
> +	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  		return 0;
> -	if ((mem->flags & MEM_Str) != 0)
> +	if (mem->type == MEM_TYPE_STR)
>  		return bytes_to_int(mem);
> -	if ((mem->flags & MEM_Real) != 0)
> +	if (mem->type == MEM_TYPE_DOUBLE)
>  		return double_to_int_precise(mem);
>  	return -1;
>  }
> @@ -787,12 +839,12 @@ mem_to_int_precise(struct Mem *mem)
>  int
>  mem_to_double(struct Mem *mem)
>  {
> -	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
> -	if ((mem->flags & MEM_Real) != 0)
> +	assert(mem->type < MEM_TYPE_INVALID);
> +	if (mem->type == MEM_TYPE_DOUBLE)
>  		return 0;
> -	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +	if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  		return int_to_double(mem);
> -	if ((mem->flags & MEM_Str) != 0)
> +	if (mem->type == MEM_TYPE_STR)
>  		return bytes_to_double(mem);
>  	return -1;
>  }
> @@ -800,12 +852,12 @@ 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)
> +	assert(mem->type < MEM_TYPE_INVALID);
> +	if (mem_is_num(mem))
>  		return 0;
> -	if ((mem->flags & MEM_Bool) != 0)
> +	if (mem->type == MEM_TYPE_BOOL)
>  		return bool_to_int(mem);
> -	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
> +	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0) {
>  		if (bytes_to_int(mem) == 0)
>  			return 0;
>  		return bytes_to_double(mem);
> @@ -816,72 +868,77 @@ mem_to_number(struct Mem *mem)
>  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)
> +	assert(mem->type < MEM_TYPE_INVALID);
> +	switch (mem->type) {
> +	case MEM_TYPE_STR:
> +		if ((mem->flags & MEM_Term) != 0)
> +			return 0;
>  		return str_to_str0(mem);
> -	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +	case MEM_TYPE_INT:
> +	case MEM_TYPE_UINT:
>  		return int_to_str0(mem);
> -	if ((mem->flags & MEM_Real) != 0)
> +	case MEM_TYPE_DOUBLE:
>  		return double_to_str0(mem);
> -	if ((mem->flags & MEM_Bool) != 0)
> +	case MEM_TYPE_BOOL:
>  		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);
> +	case MEM_TYPE_BIN:
> +		return bin_to_str0(mem);
> +	case MEM_TYPE_MAP:
> +		return map_to_str0(mem);
> +	case MEM_TYPE_ARRAY:
>  		return array_to_str0(mem);
> +	default:
> +		return -1;
>  	}
> -	return -1;
>  }
>  
>  int
>  mem_to_str(struct Mem *mem)
>  {
> -	assert((mem->flags & MEM_PURE_TYPE_MASK) != 0);
> -	if ((mem->flags & MEM_Str) != 0)
> +	assert(mem->type < MEM_TYPE_INVALID);
> +	switch (mem->type) {
> +	case MEM_TYPE_STR:
>  		return 0;
> -	if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +	case MEM_TYPE_INT:
> +	case MEM_TYPE_UINT:
>  		return int_to_str0(mem);
> -	if ((mem->flags & MEM_Real) != 0)
> +	case MEM_TYPE_DOUBLE:
>  		return double_to_str0(mem);
> -	if ((mem->flags & MEM_Bool) != 0)
> +	case MEM_TYPE_BOOL:
>  		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);
> +	case MEM_TYPE_BIN:
> +		return bin_to_str(mem);
> +	case MEM_TYPE_MAP:
> +		return map_to_str0(mem);
> +	case MEM_TYPE_ARRAY:
>  		return array_to_str0(mem);
> +	default:
> +		return -1;
>  	}
> -	return -1;
>  }
>  
>  int
>  mem_cast_explicit(struct Mem *mem, enum field_type type)
>  {
> -	if ((mem->flags & MEM_Null) != 0) {
> +	if (mem->type == MEM_TYPE_NULL) {
>  		mem->field_type = type;
>  		return 0;
>  	}
>  	switch (type) {
>  	case FIELD_TYPE_UNSIGNED:
> -		if ((mem->flags & MEM_UInt) != 0)
> +		switch (mem->type) {
> +		case MEM_TYPE_UINT:
>  			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)
> +		case MEM_TYPE_STR:
> +		case MEM_TYPE_BIN:
>  			return bytes_to_uint(mem);
> -		if ((mem->flags & MEM_Real) != 0)
> +		case MEM_TYPE_DOUBLE:
>  			return double_to_int(mem);
> -		if ((mem->flags & MEM_Bool) != 0)
> +		case MEM_TYPE_BOOL:
>  			return bool_to_int(mem);
> -		return -1;
> +		default:
> +			return -1;
> +		}
>  	case FIELD_TYPE_STRING:
>  		return mem_to_str(mem);
>  	case FIELD_TYPE_DOUBLE:
> @@ -889,26 +946,29 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
>  	case FIELD_TYPE_INTEGER:
>  		return mem_to_int(mem);
>  	case FIELD_TYPE_BOOLEAN:
> -		if ((mem->flags & MEM_Bool) != 0)
> +		switch (mem->type) {
> +		case MEM_TYPE_BOOL:
>  			return 0;
> -		if ((mem->flags & (MEM_UInt | MEM_Int)) != 0)
> +		case MEM_TYPE_INT:
> +		case MEM_TYPE_UINT:
>  			return int_to_bool(mem);
> -		if ((mem->flags & MEM_Str) != 0)
> +		case MEM_TYPE_STR:
>  			return str_to_bool(mem);
> -		if ((mem->flags & MEM_Real) != 0)
> +		case MEM_TYPE_DOUBLE:
>  			return double_to_bool(mem);
> -		return -1;
> +		default:
> +			return -1;
> +		}
>  	case FIELD_TYPE_VARBINARY:
> -		if ((mem->flags & MEM_Blob) != 0)
> -			return 0;
> -		if ((mem->flags & MEM_Str) != 0)
> +		if (mem->type == MEM_TYPE_STR)
>  			return str_to_bin(mem);
> +		if (mem_is_bytes(mem))
> +			return 0;
>  		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)
> +		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
>  			return -1;
>  		return 0;
>  	default:
> @@ -920,56 +980,56 @@ mem_cast_explicit(struct Mem *mem, enum field_type type)
>  int
>  mem_cast_implicit(struct Mem *mem, enum field_type type)
>  {
> -	if ((mem->flags & MEM_Null) != 0) {
> +	if (mem->type == MEM_TYPE_NULL) {
>  		mem->field_type = type;
>  		return 0;
>  	}
>  	switch (type) {
>  	case FIELD_TYPE_UNSIGNED:
> -		if ((mem->flags & MEM_UInt) != 0)
> +		if (mem->type == MEM_TYPE_UINT)
>  			return 0;
> -		if ((mem->flags & MEM_Real) != 0)
> +		if (mem->type == MEM_TYPE_DOUBLE)
>  			return double_to_uint(mem);
>  		return -1;
>  	case FIELD_TYPE_STRING:
> -		if ((mem->flags & MEM_Str) != 0)
> +		if (mem->type == MEM_TYPE_STR)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_DOUBLE:
> -		if ((mem->flags & MEM_Real) != 0)
> +		if (mem->type == MEM_TYPE_DOUBLE)
>  			return 0;
> -		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  			return int_to_double(mem);
>  		return -1;
>  	case FIELD_TYPE_INTEGER:
> -		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  			return 0;
> -		if ((mem->flags & MEM_Real) != 0)
> +		if (mem->type == MEM_TYPE_DOUBLE)
>  			return double_to_int(mem);
>  		return -1;
>  	case FIELD_TYPE_BOOLEAN:
> -		if ((mem->flags & MEM_Bool) != 0)
> +		if (mem->type == MEM_TYPE_BOOL)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_VARBINARY:
> -		if ((mem->flags & MEM_Blob) != 0)
> +		if ((mem->type & (MEM_TYPE_BIN | MEM_TYPE_MAP |
> +				  MEM_TYPE_ARRAY)) != 0)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_NUMBER:
> -		if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
> +		if (mem_is_num(mem))
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_MAP:
> -		if (mem_is_map(mem))
> +		if (mem->type == MEM_TYPE_MAP)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_ARRAY:
> -		if (mem_is_array(mem))
> +		if (mem->type == MEM_TYPE_ARRAY)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_SCALAR:
> -		if ((mem->flags & MEM_Blob) != 0 &&
> -		    (mem->flags & MEM_Subtype) != 0)
> +		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
>  			return -1;
>  		return 0;
>  	case FIELD_TYPE_ANY:
> @@ -983,66 +1043,65 @@ mem_cast_implicit(struct Mem *mem, enum field_type type)
>  int
>  mem_cast_implicit_old(struct Mem *mem, enum field_type type)
>  {
> -	if (mem_is_null(mem))
> +	if (mem->type == MEM_TYPE_NULL)
>  		return 0;
>  	switch (type) {
>  	case FIELD_TYPE_UNSIGNED:
> -		if ((mem->flags & MEM_UInt) != 0)
> +		if (mem->type == MEM_TYPE_UINT)
>  			return 0;
> -		if ((mem->flags & MEM_Real) != 0)
> +		if (mem->type == MEM_TYPE_DOUBLE)
>  			return double_to_uint_precise(mem);
> -		if ((mem->flags & MEM_Str) != 0)
> +		if (mem->type == MEM_TYPE_STR)
>  			return bytes_to_uint(mem);
>  		return -1;
>  	case FIELD_TYPE_STRING:
> -		if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
> +		if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
>  			return 0;
> -		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  			return int_to_str0(mem);
> -		if ((mem->flags & MEM_Real) != 0)
> +		if (mem->type == MEM_TYPE_DOUBLE)
>  			return double_to_str0(mem);
>  		return -1;
>  	case FIELD_TYPE_DOUBLE:
> -		if ((mem->flags & MEM_Real) != 0)
> +		if (mem->type == MEM_TYPE_DOUBLE)
>  			return 0;
> -		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  			return int_to_double(mem);
> -		if ((mem->flags & MEM_Str) != 0)
> +		if (mem->type == MEM_TYPE_STR)
>  			return bin_to_str(mem);
>  		return -1;
>  	case FIELD_TYPE_INTEGER:
> -		if ((mem->flags & (MEM_Int | MEM_UInt)) != 0)
> +		if ((mem->type & (MEM_TYPE_INT | MEM_TYPE_UINT)) != 0)
>  			return 0;
> -		if ((mem->flags & MEM_Str) != 0)
> +		if (mem->type == MEM_TYPE_STR)
>  			return bytes_to_int(mem);
> -		if ((mem->flags & MEM_Real) != 0)
> +		if (mem->type == MEM_TYPE_DOUBLE)
>  			return double_to_int_precise(mem);
>  		return -1;
>  	case FIELD_TYPE_BOOLEAN:
> -		if ((mem->flags & MEM_Bool) != 0)
> +		if (mem->type == MEM_TYPE_BOOL)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_VARBINARY:
> -		if ((mem->flags & MEM_Blob) != 0)
> +		if (mem->type == MEM_TYPE_BIN)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_NUMBER:
> -		if ((mem->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0)
> +		if (mem_is_num(mem))
>  			return 0;
> -		if ((mem->flags & MEM_Str) != 0)
> +		if (mem->type == MEM_TYPE_STR)
>  			return mem_to_number(mem);
>  		return -1;
>  	case FIELD_TYPE_MAP:
> -		if (mem_is_map(mem))
> +		if (mem->type == MEM_TYPE_MAP)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_ARRAY:
> -		if (mem_is_array(mem))
> +		if (mem->type == MEM_TYPE_ARRAY)
>  			return 0;
>  		return -1;
>  	case FIELD_TYPE_SCALAR:
> -		if ((mem->flags & MEM_Blob) != 0 &&
> -		    (mem->flags & MEM_Subtype) != 0)
> +		if ((mem->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
>  			return -1;
>  		return 0;
>  	default:
> @@ -1054,19 +1113,19 @@ 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)
>  {
> -	if ((mem->flags & MEM_Int) != 0) {
> +	if (mem->type == MEM_TYPE_INT) {
>  		*i = mem->u.i;
>  		*is_neg = true;
>  		return 0;
>  	}
> -	if ((mem->flags & MEM_UInt) != 0) {
> +	if (mem->type == MEM_TYPE_UINT) {
>  		*i = mem->u.i;
>  		*is_neg = false;
>  		return 0;
>  	}
> -	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0)
> +	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0)
>  		return sql_atoi64(mem->z, i, is_neg, mem->n);
> -	if ((mem->flags & MEM_Real) != 0) {
> +	if (mem->type == MEM_TYPE_DOUBLE) {
>  		double d = mem->u.r;
>  		if (d < 0 && d >= (double)INT64_MIN) {
>  			*i = (int64_t)d;
> @@ -1086,20 +1145,20 @@ mem_get_int(const struct Mem *mem, int64_t *i, bool *is_neg)
>  int
>  mem_get_uint(const struct Mem *mem, uint64_t *u)
>  {
> -	if ((mem->flags & MEM_Int) != 0)
> +	if (mem->type == MEM_TYPE_INT)
>  		return -1;
> -	if ((mem->flags & MEM_UInt) != 0) {
> +	if (mem->type == MEM_TYPE_UINT) {
>  		*u = mem->u.u;
>  		return 0;
>  	}
> -	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
> +	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 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) {
> +	if (mem->type == MEM_TYPE_DOUBLE) {
>  		double d = mem->u.r;
>  		if (d >= 0 && d < (double)UINT64_MAX) {
>  			*u = (uint64_t)d;
> @@ -1113,19 +1172,19 @@ mem_get_uint(const struct Mem *mem, uint64_t *u)
>  int
>  mem_get_double(const struct Mem *mem, double *d)
>  {
> -	if ((mem->flags & MEM_Real) != 0) {
> +	if (mem->type == MEM_TYPE_DOUBLE) {
>  		*d = mem->u.r;
>  		return 0;
>  	}
> -	if ((mem->flags & MEM_Int) != 0) {
> +	if (mem->type == MEM_TYPE_INT) {
>  		*d = (double)mem->u.i;
>  		return 0;
>  	}
> -	if ((mem->flags & MEM_UInt) != 0) {
> +	if (mem->type == MEM_TYPE_UINT) {
>  		*d = (double)mem->u.u;
>  		return 0;
>  	}
> -	if ((mem->flags & MEM_Str) != 0) {
> +	if (mem->type == MEM_TYPE_STR) {
>  		if (sqlAtoF(mem->z, d, mem->n) == 0)
>  			return -1;
>  		return 0;
> @@ -1136,7 +1195,7 @@ mem_get_double(const struct Mem *mem, double *d)
>  int
>  mem_get_bool(const struct Mem *mem, bool *b)
>  {
> -	if ((mem->flags & MEM_Bool) != 0) {
> +	if (mem->type == MEM_TYPE_BOOL) {
>  		*b = mem->u.b;
>  		return 0;
>  	}
> @@ -1146,7 +1205,7 @@ mem_get_bool(const struct Mem *mem, bool *b)
>  int
>  mem_get_str0(const struct Mem *mem, const char **s)
>  {
> -	if ((mem->flags & MEM_Str) == 0 || (mem->flags & MEM_Term) == 0)
> +	if (mem->type != MEM_TYPE_STR || (mem->flags & MEM_Term) == 0)
>  		return -1;
>  	*s = mem->z;
>  	return 0;
> @@ -1155,11 +1214,11 @@ mem_get_str0(const struct Mem *mem, const char **s)
>  int
>  mem_get_bin(const struct Mem *mem, const char **s)
>  {
> -	if ((mem->flags & MEM_Str) != 0) {
> +	if (mem->type == MEM_TYPE_STR) {
>  		*s = mem->n > 0 ? mem->z : NULL;
>  		return 0;
>  	}
> -	if ((mem->flags & MEM_Blob) == 0 || (mem->flags & MEM_Zero) != 0)
> +	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
>  		return -1;
>  	*s = mem->z;
>  	return 0;
> @@ -1168,9 +1227,9 @@ mem_get_bin(const struct Mem *mem, const char **s)
>  int
>  mem_len(const struct Mem *mem, uint32_t *len)
>  {
> -	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
> +	if (!mem_is_bytes(mem))
>  		return -1;
> -	if ((mem->flags & MEM_Blob) !=0 && (mem->flags & MEM_Zero) != 0)
> +	if (mem->type == MEM_TYPE_BIN && (mem->flags & MEM_Zero) != 0)
>  		*len = mem->n + mem->u.nZero;
>  	else
>  		*len = mem->n;
> @@ -1180,7 +1239,7 @@ mem_len(const struct Mem *mem, uint32_t *len)
>  int
>  mem_get_agg(const struct Mem *mem, void **accum)
>  {
> -	if ((mem->flags & MEM_Agg) == 0)
> +	if (mem->type != MEM_TYPE_AGG)
>  		return -1;
>  	*accum = mem->z;
>  	return 0;
> @@ -1191,16 +1250,17 @@ mem_copy(struct Mem *to, const struct Mem *from)
>  {
>  	mem_clear(to);
>  	to->u = from->u;
> +	to->type = from->type;
>  	to->flags = from->flags;
> -	to->subtype = from->subtype;
>  	to->field_type = from->field_type;
>  	to->n = from->n;
>  	to->z = from->z;
> -	if ((to->flags & (MEM_Str | MEM_Blob)) == 0)
> +	if (!mem_is_bytes(to))
>  		return 0;
>  	if ((to->flags & MEM_Static) != 0)
>  		return 0;
> -	if ((to->flags & (MEM_Zero | MEM_Blob)) == (MEM_Zero | MEM_Blob))
> +	assert((to->flags & MEM_Zero) == 0 || to->type == MEM_TYPE_BIN);
> +	if ((to->flags & MEM_Zero) != 0)
>  		return sqlVdbeMemExpandBlob(to);
>  	to->zMalloc = sqlDbReallocOrFree(to->db, to->zMalloc, to->n);
>  	if (to->zMalloc == NULL)
> @@ -1208,7 +1268,7 @@ mem_copy(struct Mem *to, const struct Mem *from)
>  	to->szMalloc = sqlDbMallocSize(to->db, to->zMalloc);
>  	memcpy(to->zMalloc, to->z, to->n);
>  	to->z = to->zMalloc;
> -	to->flags &= (MEM_Str | MEM_Blob | MEM_Term | MEM_Subtype);
> +	to->flags &= MEM_Term;
>  	return 0;
>  }
>  
> @@ -1217,16 +1277,16 @@ mem_copy_as_ephemeral(struct Mem *to, const struct Mem *from)
>  {
>  	mem_clear(to);
>  	to->u = from->u;
> +	to->type = from->type;
>  	to->flags = from->flags;
> -	to->subtype = from->subtype;
>  	to->field_type = from->field_type;
>  	to->n = from->n;
>  	to->z = from->z;
> -	if ((to->flags & (MEM_Str | MEM_Blob)) == 0)
> +	if (!mem_is_bytes(to))
>  		return;
>  	if ((to->flags & (MEM_Static | MEM_Ephem)) != 0)
>  		return;
> -	to->flags &= (MEM_Str | MEM_Blob | MEM_Term | MEM_Zero | MEM_Subtype);
> +	to->flags &= MEM_Term | MEM_Zero;
>  	to->flags |= MEM_Ephem;
>  	return;
>  }
> @@ -1236,7 +1296,8 @@ mem_move(struct Mem *to, struct Mem *from)
>  {
>  	mem_destroy(to);
>  	memcpy(to, from, sizeof(*to));
> -	from->flags = MEM_Null;
> +	from->type = MEM_TYPE_NULL;
> +	from->flags = 0;
>  	from->szMalloc = 0;
>  	from->zMalloc = NULL;
>  }
> @@ -1247,7 +1308,7 @@ try_return_null(const struct Mem *a, const struct Mem *b, struct Mem *result,
>  {
>  	mem_clear(result);
>  	result->field_type = type;
> -	return (((a->flags | b->flags) & MEM_Null) != 0);
> +	return ((a->type | b->type) & MEM_TYPE_NULL) != 0;
>  }
>  
>  int
> @@ -1258,7 +1319,7 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
>  		if (try_return_null(a, b, result, FIELD_TYPE_STRING))
>  			return 0;
>  	} else {
> -		if (((a->flags | b->flags) & MEM_Null) != 0) {
> +		if (((a->type | b->type) & MEM_TYPE_NULL) != 0) {
>  			mem_clear(a);
>  			result->field_type = FIELD_TYPE_STRING;
>  			return 0;
> @@ -1266,19 +1327,19 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
>  	}
>  
>  	/* Concatenation operation can be applied only to strings and blobs. */
> -	if ((b->flags & (MEM_Str | MEM_Blob)) == 0) {
> +	if (((b->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)) {
>  		diag_set(ClientError, ER_INCONSISTENT_TYPES,
>  			 "text or varbinary", mem_type_to_str(b));
>  		return -1;
>  	}
> -	if ((a->flags & (MEM_Str | MEM_Blob)) == 0) {
> +	if (((a->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)) {
>  		diag_set(ClientError, ER_INCONSISTENT_TYPES,
>  			 "text or varbinary", mem_type_to_str(a));
>  		return -1;
>  	}
>  
>  	/* Moreover, both operands must be of the same type. */
> -	if ((b->flags & MEM_Str) != (a->flags & MEM_Str)) {
> +	if (b->type != a->type) {
>  		diag_set(ClientError, ER_INCONSISTENT_TYPES,
>  			 mem_type_to_str(a), mem_type_to_str(b));
>  		return -1;
> @@ -1295,8 +1356,9 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
>  	if (sqlVdbeMemGrow(result, size, result == a) != 0)
>  		return -1;
>  
> -	result->flags = a->flags & (MEM_Str | MEM_Blob);
> -	if ((result->flags & MEM_Blob) != 0)
> +	result->type = a->type == MEM_TYPE_STR ? MEM_TYPE_STR : MEM_TYPE_BIN;
> +	result->flags = 0;
> +	if (result->type == MEM_TYPE_BIN)
>  		result->field_type = FIELD_TYPE_VARBINARY;
>  	if (result != a)
>  		memcpy(result->z, a->z, a->n);
> @@ -1311,36 +1373,34 @@ struct sql_num {
>  		uint64_t u;
>  		double d;
>  	};
> -	int type;
> +	enum mem_type type;
>  	bool is_neg;
>  };
>  
>  static int
>  get_number(const struct Mem *mem, struct sql_num *number)
>  {
> -	if ((mem->flags & MEM_Real) != 0) {
> +	if (mem->type == MEM_TYPE_DOUBLE) {
>  		number->d = mem->u.r;
> -		number->type = MEM_Real;
> +		number->type = MEM_TYPE_DOUBLE;
>  		return 0;
>  	}
> -	if ((mem->flags & MEM_Int) != 0) {
> +	if (mem->type == MEM_TYPE_INT) {
>  		number->i = mem->u.i;
> -		number->type = MEM_Int;
> +		number->type = MEM_TYPE_INT;
>  		number->is_neg = true;
>  		return 0;
>  	}
> -	if ((mem->flags & MEM_UInt) != 0) {
> +	if (mem->type == MEM_TYPE_UINT) {
>  		number->u = mem->u.u;
> -		number->type = MEM_UInt;
> +		number->type = MEM_TYPE_UINT;
>  		number->is_neg = false;
>  		return 0;
>  	}
> -	if ((mem->flags & (MEM_Str | MEM_Blob)) == 0)
> -		return -1;
> -	if ((mem->flags & MEM_Subtype) != 0)
> +	if ((mem->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) == 0)
>  		return -1;
>  	if (sql_atoi64(mem->z, &number->i, &number->is_neg, mem->n) == 0) {
> -		number->type = number->is_neg ? MEM_Int : MEM_UInt;
> +		number->type = number->is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
>  		/*
>  		 * The next line should be removed along with the is_neg field
>  		 * of struct sql_num. The integer type tells us about the sign.
> @@ -1351,7 +1411,7 @@ get_number(const struct Mem *mem, struct sql_num *number)
>  		return 0;
>  	}
>  	if (sqlAtoF(mem->z, &number->d, mem->n) != 0) {
> -		number->type = MEM_Real;
> +		number->type = MEM_TYPE_DOUBLE;
>  		return 0;
>  	}
>  	return -1;
> @@ -1372,16 +1432,16 @@ arithmetic_prepare(const struct Mem *left, const struct Mem *right,
>  		return -1;
>  	}
>  	assert(a->type != 0 && b->type != 0);
> -	if (a->type == MEM_Real && b->type != MEM_Real) {
> -		b->d = b->type == MEM_Int ? (double)b->i : (double)b->u;
> -		b->type = MEM_Real;
> +	if (a->type == b->type || ((a->type | b->type) & MEM_TYPE_DOUBLE) == 0)
>  		return 0;
> -	}
> -	if (a->type != MEM_Real && b->type == MEM_Real) {
> -		a->d = a->type == MEM_Int ? (double)a->i : (double)a->u;
> -		a->type = MEM_Real;
> +	if (a->type == MEM_TYPE_DOUBLE) {
> +		b->d = b->type == MEM_TYPE_INT ? (double)b->i : (double)b->u;
> +		b->type = MEM_TYPE_DOUBLE;
>  		return 0;
>  	}
> +	assert(b->type == MEM_TYPE_DOUBLE);
> +	a->d = a->type == MEM_TYPE_INT ? (double)a->i : (double)a->u;
> +	a->type = MEM_TYPE_DOUBLE;
>  	return 0;
>  }
>  
> @@ -1395,10 +1455,11 @@ mem_add(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  	if (arithmetic_prepare(left, right, &a, &b) != 0)
>  		return -1;
>  
> -	assert(a.type != MEM_Real || a.type == b.type);
> -	if (a.type == MEM_Real) {
> +	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
> +	if (a.type == MEM_TYPE_DOUBLE) {
>  		result->u.r = a.d + b.d;
> -		result->flags = MEM_Real;
> +		result->type = MEM_TYPE_DOUBLE;
> +		assert(result->flags == 0);
>  		return 0;
>  	}
>  
> @@ -1409,7 +1470,8 @@ mem_add(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  		return -1;
>  	}
>  	result->u.i = res;
> -	result->flags = is_neg ? MEM_Int : MEM_UInt;
> +	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(result->flags == 0);
>  	return 0;
>  }
>  
> @@ -1423,10 +1485,11 @@ mem_sub(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  	if (arithmetic_prepare(left, right, &a, &b) != 0)
>  		return -1;
>  
> -	assert(a.type != MEM_Real || a.type == b.type);
> -	if (a.type == MEM_Real) {
> +	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
> +	if (a.type == MEM_TYPE_DOUBLE) {
>  		result->u.r = a.d - b.d;
> -		result->flags = MEM_Real;
> +		result->type = MEM_TYPE_DOUBLE;
> +		assert(result->flags == 0);
>  		return 0;
>  	}
>  
> @@ -1437,7 +1500,8 @@ mem_sub(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  		return -1;
>  	}
>  	result->u.i = res;
> -	result->flags = is_neg ? MEM_Int : MEM_UInt;
> +	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(result->flags == 0);
>  	return 0;
>  }
>  
> @@ -1451,10 +1515,11 @@ mem_mul(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  	if (arithmetic_prepare(left, right, &a, &b) != 0)
>  		return -1;
>  
> -	assert(a.type != MEM_Real || a.type == b.type);
> -	if (a.type == MEM_Real) {
> +	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
> +	if (a.type == MEM_TYPE_DOUBLE) {
>  		result->u.r = a.d * b.d;
> -		result->flags = MEM_Real;
> +		result->type = MEM_TYPE_DOUBLE;
> +		assert(result->flags == 0);
>  		return 0;
>  	}
>  
> @@ -1465,7 +1530,8 @@ mem_mul(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  		return -1;
>  	}
>  	result->u.i = res;
> -	result->flags = is_neg ? MEM_Int : MEM_UInt;
> +	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(result->flags == 0);
>  	return 0;
>  }
>  
> @@ -1479,15 +1545,16 @@ mem_div(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  	if (arithmetic_prepare(left, right, &a, &b) != 0)
>  		return -1;
>  
> -	assert(a.type != MEM_Real || a.type == b.type);
> -	if (a.type == MEM_Real) {
> +	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
> +	if (a.type == MEM_TYPE_DOUBLE) {
>  		if (b.d == 0.) {
>  			diag_set(ClientError, ER_SQL_EXECUTE,
>  				 "division by zero");
>  			return -1;
>  		}
>  		result->u.r = a.d / b.d;
> -		result->flags = MEM_Real;
> +		result->type = MEM_TYPE_DOUBLE;
> +		assert(result->flags == 0);
>  		return 0;
>  	}
>  
> @@ -1502,7 +1569,8 @@ mem_div(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  		return -1;
>  	}
>  	result->u.i = res;
> -	result->flags = is_neg ? MEM_Int : MEM_UInt;
> +	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(result->flags == 0);
>  	return 0;
>  }
>  
> @@ -1516,14 +1584,14 @@ mem_rem(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  	if (arithmetic_prepare(left, right, &a, &b) != 0)
>  		return -1;
>  
> -	assert(a.type != MEM_Real || a.type == b.type);
> +	assert(a.type != MEM_TYPE_DOUBLE || a.type == b.type);
>  	/*
>  	 * TODO: This operation works wrong when double d > INT64_MAX and
>  	 * d < UINT64_MAX. Also, there may be precision losses due to
>  	 * conversion integer to double and back.
>  	 */
> -	a.i = a.type == MEM_Real ? (int64_t)a.d : a.i;
> -	b.i = b.type == MEM_Real ? (int64_t)b.d : b.i;
> +	a.i = a.type == MEM_TYPE_DOUBLE ? (int64_t)a.d : a.i;
> +	b.i = b.type == MEM_TYPE_DOUBLE ? (int64_t)b.d : b.i;
>  	if (b.i == 0) {
>  		diag_set(ClientError, ER_SQL_EXECUTE, "division by zero");
>  		return -1;
> @@ -1535,7 +1603,8 @@ mem_rem(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  		return -1;
>  	}
>  	result->u.i = res;
> -	result->flags = is_neg ? MEM_Int : MEM_UInt;
> +	result->type = is_neg ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(result->flags == 0);
>  	return 0;
>  }
>  
> @@ -1567,7 +1636,8 @@ mem_bit_and(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  	if (bitwise_prepare(left, right, &a, &b) != 0)
>  		return -1;
>  	result->u.i = a & b;
> -	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
> +	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(result->flags == 0);
>  	return 0;
>  }
>  
> @@ -1581,7 +1651,8 @@ mem_bit_or(const struct Mem *left, const struct Mem *right, struct Mem *result)
>  	if (bitwise_prepare(left, right, &a, &b) != 0)
>  		return -1;
>  	result->u.i = a | b;
> -	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
> +	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(result->flags == 0);
>  	return 0;
>  }
>  
> @@ -1603,7 +1674,8 @@ mem_shift_left(const struct Mem *left, const struct Mem *right,
>  		result->u.i = 0;
>  	else
>  		result->u.i = a << b;
> -	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
> +	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(result->flags == 0);
>  	return 0;
>  }
>  
> @@ -1625,7 +1697,8 @@ mem_shift_right(const struct Mem *left, const struct Mem *right,
>  		result->u.i = a >= 0 ? 0 : -1;
>  	else
>  		result->u.i = a >> b;
> -	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
> +	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(result->flags == 0);
>  	return 0;
>  }
>  
> @@ -1634,7 +1707,7 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
>  {
>  	mem_clear(result);
>  	result->field_type = FIELD_TYPE_INTEGER;
> -	if ((mem->flags & MEM_Null) != 0)
> +	if (mem->type == MEM_TYPE_NULL)
>  		return 0;
>  	int64_t i;
>  	bool unused;
> @@ -1644,14 +1717,15 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
>  		return -1;
>  	}
>  	result->u.i = ~i;
> -	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
> +	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(result->flags == 0);
>  	return 0;
>  }
>  
>  int
>  mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
>  {
> -	if ((a->flags & b->flags & MEM_Bool) == 0)
> +	if (a->type != MEM_TYPE_BOOL || b->type != MEM_TYPE_BOOL)
>  		return -1;
>  	if (a->u.b == b->u.b)
>  		*result = 0;
> @@ -1665,7 +1739,7 @@ mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
>  int
>  mem_cmp_bin(const struct Mem *a, const struct Mem *b, int *result)
>  {
> -	if ((a->flags & b->flags & MEM_Blob) == 0)
> +	if ((a->type & b->type & MEM_TYPE_BIN) == 0)
>  		return -1;
>  	int an = a->n;
>  	int bn = b->n;
> @@ -1722,8 +1796,8 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
>  	}
>  	if (get_number(left, &a) != 0)
>  		return -1;
> -	if (a.type == MEM_Real) {
> -		if (b.type == MEM_Real) {
> +	if (a.type == MEM_TYPE_DOUBLE) {
> +		if (b.type == MEM_TYPE_DOUBLE) {
>  			if (a.d > b.d)
>  				*result = 1;
>  			else if (a.d < b.d)
> @@ -1732,14 +1806,14 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
>  				*result = 0;
>  			return 0;
>  		}
> -		if (b.type == MEM_Int)
> +		if (b.type == MEM_TYPE_INT)
>  			*result = double_compare_nint64(a.d, b.i, 1);
>  		else
>  			*result = double_compare_uint64(a.d, b.u, 1);
>  		return 0;
>  	}
> -	if (a.type == MEM_Int) {
> -		if (b.type == MEM_Int) {
> +	if (a.type == MEM_TYPE_INT) {
> +		if (b.type == MEM_TYPE_INT) {
>  			if (a.i > b.i)
>  				*result = 1;
>  			else if (a.i < b.i)
> @@ -1748,14 +1822,14 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
>  				*result = 0;
>  			return 0;
>  		}
> -		if (b.type == MEM_UInt)
> +		if (b.type == MEM_TYPE_UINT)
>  			*result = -1;
>  		else
>  			*result = double_compare_nint64(b.d, a.i, -1);
>  		return 0;
>  	}
> -	assert(a.type == MEM_UInt);
> -	if (b.type == MEM_UInt) {
> +	assert(a.type == MEM_TYPE_UINT);
> +	if (b.type == MEM_TYPE_UINT) {
>  		if (a.u > b.u)
>  			*result = 1;
>  		else if (a.u < b.u)
> @@ -1764,7 +1838,7 @@ mem_cmp_num(const struct Mem *left, const struct Mem *right, int *result)
>  			*result = 0;
>  		return 0;
>  	}
> -	if (b.type == MEM_Int)
> +	if (b.type == MEM_TYPE_INT)
>  		*result = 1;
>  	else
>  		*result = double_compare_uint64(b.d, a.u, -1);
> @@ -1778,15 +1852,15 @@ mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
>  	char *a;
>  	uint32_t an;
>  	char bufl[BUF_SIZE];
> -	if ((left->flags & MEM_Str) != 0) {
> +	if (left->type == MEM_TYPE_STR) {
>  		a = left->z;
>  		an = left->n;
>  	} else {
> -		assert((left->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0);
> +		assert(mem_is_num(left));
>  		a = &bufl[0];
> -		if ((left->flags & MEM_Int) != 0)
> +		if (left->type == MEM_TYPE_INT)
>  			sql_snprintf(BUF_SIZE, a, "%lld", left->u.i);
> -		else if ((left->flags & MEM_UInt) != 0)
> +		else if (left->type == MEM_TYPE_UINT)
>  			sql_snprintf(BUF_SIZE, a, "%llu", left->u.u);
>  		else
>  			sql_snprintf(BUF_SIZE, a, "%!.15g", left->u.r);
> @@ -1796,15 +1870,15 @@ mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
>  	char *b;
>  	uint32_t bn;
>  	char bufr[BUF_SIZE];
> -	if ((right->flags & MEM_Str) != 0) {
> +	if (right->type == MEM_TYPE_STR) {
>  		b = right->z;
>  		bn = right->n;
>  	} else {
> -		assert((right->flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0);
> +		assert(mem_is_num(right));
>  		b = &bufr[0];
> -		if ((right->flags & MEM_Int) != 0)
> +		if (right->type == MEM_TYPE_INT)
>  			sql_snprintf(BUF_SIZE, b, "%lld", right->u.i);
> -		else if ((right->flags & MEM_UInt) != 0)
> +		else if (right->type == MEM_TYPE_UINT)
>  			sql_snprintf(BUF_SIZE, b, "%llu", right->u.u);
>  		else
>  			sql_snprintf(BUF_SIZE, b, "%!.15g", right->u.r);
> @@ -1822,13 +1896,6 @@ mem_cmp_str(const struct Mem *left, const struct Mem *right, int *result,
>  	return 0;
>  }
>  
> -static inline bool
> -mem_has_msgpack_subtype(struct Mem *mem)
> -{
> -	return (mem->flags & MEM_Subtype) != 0 &&
> -	       mem->subtype == SQL_SUBTYPE_MSGPACK;
> -}
> -
>  /*
>   * Both *pMem1 and *pMem2 contain string values. Compare the two values
>   * using the collation sequence pColl. As usual, return a negative , zero
> @@ -1864,20 +1931,22 @@ char *
>  mem_type_to_str(const struct Mem *p)
>  {
>  	assert(p != NULL);
> -	switch (p->flags & MEM_PURE_TYPE_MASK) {
> -	case MEM_Null:
> +	switch (p->type) {
> +	case MEM_TYPE_NULL:
>  		return "NULL";
> -	case MEM_Str:
> +	case MEM_TYPE_STR:
>  		return "text";
> -	case MEM_Int:
> +	case MEM_TYPE_INT:
>  		return "integer";
> -	case MEM_UInt:
> +	case MEM_TYPE_UINT:
>  		return "unsigned";
> -	case MEM_Real:
> +	case MEM_TYPE_DOUBLE:
>  		return "real";
> -	case MEM_Blob:
> +	case MEM_TYPE_ARRAY:
> +	case MEM_TYPE_MAP:
> +	case MEM_TYPE_BIN:
>  		return "varbinary";
> -	case MEM_Bool:
> +	case MEM_TYPE_BOOL:
>  		return "boolean";
>  	default:
>  		unreachable();
> @@ -1887,28 +1956,32 @@ mem_type_to_str(const struct Mem *p)
>  enum mp_type
>  mem_mp_type(struct Mem *mem)
>  {
> -	switch (mem->flags & MEM_PURE_TYPE_MASK) {
> -	case MEM_Int:
> -		return MP_INT;
> -	case MEM_UInt:
> +	assert(mem->type < MEM_TYPE_INVALID);
> +	switch (mem->type) {
> +	case MEM_TYPE_NULL:
> +		return MP_NIL;
> +	case MEM_TYPE_UINT:
>  		return MP_UINT;
> -	case MEM_Real:
> -		return MP_DOUBLE;
> -	case MEM_Str:
> +	case MEM_TYPE_INT:
> +		return MP_INT;
> +	case MEM_TYPE_STR:
>  		return MP_STR;
> -	case MEM_Blob:
> -		if ((mem->flags & MEM_Subtype) == 0 ||
> -		     mem->subtype != SQL_SUBTYPE_MSGPACK)
> -			return MP_BIN;
> -		assert(mp_typeof(*mem->z) == MP_MAP ||
> -		       mp_typeof(*mem->z) == MP_ARRAY);
> -		return mp_typeof(*mem->z);
> -	case MEM_Bool:
> +	case MEM_TYPE_BIN:
> +		return MP_BIN;
> +	case MEM_TYPE_ARRAY:
> +		return MP_ARRAY;
> +	case MEM_TYPE_MAP:
> +		return MP_MAP;
> +	case MEM_TYPE_BOOL:
>  		return MP_BOOL;
> -	case MEM_Null:
> -		return MP_NIL;
> -	default: unreachable();
> +	case MEM_TYPE_FLOAT:
> +		return MP_FLOAT;
> +	case MEM_TYPE_DOUBLE:
> +		return MP_DOUBLE;
> +	default:
> +		unreachable();
>  	}
> +	return MP_NIL;
>  }
>  
>  /* EVIDENCE-OF: R-12793-43283 Every value in sql has one of five
> @@ -1944,11 +2017,6 @@ sqlVdbeCheckMemInvariants(Mem * p)
>  	 */
>  	assert((p->flags & MEM_Dyn) == 0 || p->szMalloc == 0);
>  
> -	/* Cannot be both MEM_Int and MEM_Real at the same time */
> -	assert((p->flags & (MEM_Int | MEM_Real)) != (MEM_Int | MEM_Real));
> -	/* Can't be both UInt and Int at the same time.  */
> -	assert((p->flags & (MEM_Int | MEM_UInt)) != (MEM_Int | MEM_UInt));
> -
>  	/* The szMalloc field holds the correct memory allocation size */
>  	assert(p->szMalloc == 0 ||
>  	       p->szMalloc == sqlDbMallocSize(p->db, p->zMalloc));
> @@ -1961,7 +2029,7 @@ sqlVdbeCheckMemInvariants(Mem * p)
>  	 *   (3) An ephemeral string or blob
>  	 *   (4) A static string or blob
>  	 */
> -	if ((p->flags & (MEM_Str | MEM_Blob)) && p->n > 0) {
> +	if ((p->type & (MEM_TYPE_STR | MEM_TYPE_BIN)) != 0 && p->n > 0) {
>  		assert(((p->szMalloc > 0 && p->z == p->zMalloc) ? 1 : 0) +
>  		       ((p->flags & MEM_Dyn) != 0 ? 1 : 0) +
>  		       ((p->flags & MEM_Ephem) != 0 ? 1 : 0) +
> @@ -1980,7 +2048,7 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
>  	char *zCsr = zBuf;
>  	int f = pMem->flags;
>  
> -	if (f&MEM_Blob) {
> +	if (pMem->type == MEM_TYPE_BIN) {
>  		int i;
>  		char c;
>  		if (f & MEM_Dyn) {
> @@ -2016,7 +2084,7 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
>  			zCsr += sqlStrlen30(zCsr);
>  		}
>  		*zCsr = '\0';
> -	} else if (f & MEM_Str) {
> +	} else if (pMem->type == MEM_TYPE_STR) {
>  		int j, k;
>  		zBuf[0] = ' ';
>  		if (f & MEM_Dyn) {
> @@ -2056,26 +2124,34 @@ sqlVdbeMemPrettyPrint(Mem *pMem, char *zBuf)
>  static void
>  memTracePrint(Mem *p)
>  {
> -	if (p->flags & MEM_Undefined) {
> -		printf(" undefined");
> -	} else if (p->flags & MEM_Null) {
> +	switch (p->type) {
> +	case MEM_TYPE_NULL:
>  		printf(" NULL");
> -	} else if ((p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str)) {
> -		printf(" si:%lld", p->u.i);
> -	} else if (p->flags & MEM_Int) {
> +		return;
> +	case MEM_TYPE_INT:
>  		printf(" i:%lld", p->u.i);
> -	} else if (p->flags & MEM_UInt) {
> +		return;
> +	case MEM_TYPE_UINT:
>  		printf(" u:%"PRIu64"", p->u.u);
> -	} else if (p->flags & MEM_Real) {
> +		return;
> +	case MEM_TYPE_DOUBLE:
>  		printf(" r:%g", p->u.r);
> -	} else if (p->flags & MEM_Bool) {
> +		return;
> +	case MEM_TYPE_INVALID:
> +		printf(" undefined");
> +		return;
> +	case MEM_TYPE_BOOL:
>  		printf(" bool:%s", SQL_TOKEN_BOOLEAN(p->u.b));
> -	} else {
> +		return;
> +	default: {
>  		char zBuf[200];
>  		sqlVdbeMemPrettyPrint(p, zBuf);
>  		printf(" %s", zBuf);
> +		if ((p->type & (MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0)
> +			printf(" subtype=0x%02x", SQL_SUBTYPE_MSGPACK);
> +		return;
> +	}
>  	}
> -	if (p->flags & MEM_Subtype) printf(" subtype=0x%02x", p->subtype);
>  }
>  
>  void
> @@ -2095,7 +2171,7 @@ sqlVdbeMemExpandBlob(Mem * pMem)
>  {
>  	int nByte;
>  	assert(pMem->flags & MEM_Zero);
> -	assert(pMem->flags & MEM_Blob);
> +	assert(pMem->type == MEM_TYPE_BIN);
>  
>  	/* Set nByte to the number of bytes required to store the expanded blob. */
>  	nByte = pMem->n + pMem->u.nZero;
> @@ -2121,7 +2197,7 @@ sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
>  	/* If the bPreserve flag is set to true, then the memory cell must already
>  	 * contain a valid string or blob value.
>  	 */
> -	assert(bPreserve == 0 || pMem->flags & (MEM_Blob | MEM_Str));
> +	assert(bPreserve == 0 || mem_is_bytes(pMem));
>  	testcase(bPreserve && pMem->z == 0);
>  
>  	assert(pMem->szMalloc == 0 ||
> @@ -2168,9 +2244,8 @@ sqlVdbeMemGrow(struct Mem *pMem, int n, int bPreserve)
>   * routine is a no-op.
>   *
>   * Any prior string or blob content in the pMem object may be discarded.
> - * The pMem->xDel destructor is called, if it exists.  Though MEM_Str
> - * and MEM_Blob values may be discarded, MEM_Int, MEM_Real, and MEM_Null
> - * values are preserved.
> + * The pMem->xDel destructor is called, if it exists. Though STRING, VARBINARY,
> + * MAP and ARRAY values may be discarded, all other values are preserved.
>   *
>   * Return 0 on success or -1 if unable to complete the resizing.
>   */
> @@ -2184,7 +2259,6 @@ sqlVdbeMemClearAndResize(Mem * pMem, int szNew)
>  	}
>  	assert((pMem->flags & MEM_Dyn) == 0);
>  	pMem->z = pMem->zMalloc;
> -	pMem->flags &= (MEM_Null | MEM_Int | MEM_Real);
>  	return 0;
>  }
>  
> @@ -2208,7 +2282,8 @@ sqlValueNew(sql * db)
>  {
>  	Mem *p = sqlDbMallocZero(db, sizeof(*p));
>  	if (p) {
> -		p->flags = MEM_Null;
> +		p->type = MEM_TYPE_NULL;
> +		assert(p->flags == 0);
>  		p->db = db;
>  	}
>  	return p;
> @@ -2223,7 +2298,8 @@ releaseMemArray(Mem * p, int N)
>  			assert((&p[1]) == pEnd || p[0].db == p[1].db);
>  			assert(sqlVdbeCheckMemInvariants(p));
>  			mem_destroy(p);
> -			p->flags = MEM_Undefined;
> +			p->type = MEM_TYPE_INVALID;
> +			assert(p->flags == 0);
>  		} while ((++p) < pEnd);
>  	}
>  }
> @@ -2236,7 +2312,7 @@ int
>  sqlVdbeMemTooBig(Mem * p)
>  {
>  	assert(p->db != 0);
> -	if (p->flags & (MEM_Str | MEM_Blob)) {
> +	if (mem_is_bytes(p)) {
>  		int n = p->n;
>  		if (p->flags & MEM_Zero) {
>  			n += p->u.nZero;
> @@ -2258,40 +2334,38 @@ sqlVdbeMemTooBig(Mem * p)
>  int
>  sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
>  {
> -	int f1, f2;
>  	int res;
> -	int combined_flags;
>  
> -	f1 = pMem1->flags;
> -	f2 = pMem2->flags;
> -	combined_flags = f1 | f2;
> +	enum mem_type type1 = pMem1->type;
> +	enum mem_type type2 = pMem2->type;
>  
>  	/* If one value is NULL, it is less than the other. If both values
>  	 * are NULL, return 0.
>  	 */
> -	if (combined_flags & MEM_Null) {
> -		return (f2 & MEM_Null) - (f1 & MEM_Null);
> -	}
> +	if (((type1 | type2) & MEM_TYPE_NULL) != 0)
> +		return (int)(type2 == MEM_TYPE_NULL) -
> +		       (int)(type1 == MEM_TYPE_NULL);
>  
> -	if ((combined_flags & MEM_Bool) != 0) {
> -		if ((f1 & f2 & MEM_Bool) != 0) {
> +	if (((type1 | type2) & MEM_TYPE_BOOL) != 0) {
> +		if (type1 == MEM_TYPE_BOOL && type2 == MEM_TYPE_BOOL) {
>  			if (pMem1->u.b == pMem2->u.b)
>  				return 0;
>  			if (pMem1->u.b)
>  				return 1;
>  			return -1;
>  		}
> -		if ((f2 & MEM_Bool) != 0)
> +		if (type2 == MEM_TYPE_BOOL)
>  			return +1;
>  		return -1;
>  	}
>  
>  	/* At least one of the two values is a number
>  	 */
> -	if ((combined_flags & (MEM_Int | MEM_UInt | MEM_Real)) != 0) {
> -		if ((f1 & (MEM_Real | MEM_Int | MEM_UInt)) == 0)
> +	if (((type1 | type2) &
> +	     (MEM_TYPE_INT | MEM_TYPE_UINT | MEM_TYPE_DOUBLE)) != 0) {
> +		if (!mem_is_num(pMem1))
>  			return +1;
> -		if ((f2 & (MEM_Real | MEM_Int | MEM_UInt)) == 0)
> +		if (!mem_is_num(pMem2))
>  			return -1;
>  		mem_cmp_num(pMem1, pMem2, &res);
>  		return res;
> @@ -2300,11 +2374,11 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
>  	/* If one value is a string and the other is a blob, the string is less.
>  	 * If both are strings, compare using the collating functions.
>  	 */
> -	if (combined_flags & MEM_Str) {
> -		if ((f1 & MEM_Str) == 0) {
> +	if (((type1 | type2) & MEM_TYPE_STR) != 0) {
> +		if (type1 != MEM_TYPE_STR) {
>  			return 1;
>  		}
> -		if ((f2 & MEM_Str) == 0) {
> +		if (type2 != MEM_TYPE_STR) {
>  			return -1;
>  		}
>  		mem_cmp_str(pMem1, pMem2, &res, pColl);
> @@ -2322,12 +2396,13 @@ sql_vdbemem_finalize(struct Mem *mem, struct func *func)
>  	assert(func != NULL);
>  	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
>  	assert(func->def->aggregate == FUNC_AGGREGATE_GROUP);
> -	assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
> +	assert(mem->type == MEM_TYPE_NULL || func == mem->u.func);
>  	sql_context ctx;
>  	memset(&ctx, 0, sizeof(ctx));
>  	Mem t;
>  	memset(&t, 0, sizeof(t));
> -	t.flags = MEM_Null;
> +	t.type = MEM_TYPE_NULL;
> +	t.flags = 0;
>  	t.db = mem->db;
>  	t.field_type = field_type_MAX;
>  	ctx.pOut = &t;
> @@ -2356,35 +2431,35 @@ sqlVdbeCompareMsgpack(const char **key1,
>  			break;
>  		}
>  	case MP_NIL:{
> -			rc = -((pKey2->flags & MEM_Null) == 0);
> +			rc = -(pKey2->type != MEM_TYPE_NULL);
>  			mp_decode_nil(&aKey1);
>  			break;
>  		}
>  	case MP_BOOL:{
>  			mem1.u.b = mp_decode_bool(&aKey1);
> -			if ((pKey2->flags & MEM_Bool) != 0) {
> +			if (pKey2->type == MEM_TYPE_BOOL) {
>  				if (mem1.u.b != pKey2->u.b)
>  					rc = mem1.u.b ? 1 : -1;
>  			} else {
> -				rc = (pKey2->flags & MEM_Null) != 0 ? 1 : -1;
> +				rc = pKey2->type == MEM_TYPE_NULL ? 1 : -1;
>  			}
>  			break;
>  		}
>  	case MP_UINT:{
>  			mem1.u.u = mp_decode_uint(&aKey1);
> -			if ((pKey2->flags & MEM_Int) != 0) {
> +			if (pKey2->type == MEM_TYPE_INT) {
>  				rc = +1;
> -			} else if ((pKey2->flags & MEM_UInt) != 0) {
> +			} else if (pKey2->type == MEM_TYPE_UINT) {
>  				if (mem1.u.u < pKey2->u.u)
>  					rc = -1;
>  				else if (mem1.u.u > pKey2->u.u)
>  					rc = +1;
> -			} else if ((pKey2->flags & MEM_Real) != 0) {
> +			} else if (pKey2->type == MEM_TYPE_DOUBLE) {
>  				rc = double_compare_uint64(pKey2->u.r,
>  							   mem1.u.u, -1);
> -			} else if ((pKey2->flags & MEM_Null) != 0) {
> +			} else if (pKey2->type == MEM_TYPE_NULL) {
>  				rc = 1;
> -			} else if ((pKey2->flags & MEM_Bool) != 0) {
> +			} else if (pKey2->type == MEM_TYPE_BOOL) {
>  				rc = 1;
>  			} else {
>  				rc = -1;
> @@ -2393,20 +2468,20 @@ sqlVdbeCompareMsgpack(const char **key1,
>  		}
>  	case MP_INT:{
>  			mem1.u.i = mp_decode_int(&aKey1);
> -			if ((pKey2->flags & MEM_UInt) != 0) {
> +			if (pKey2->type == MEM_TYPE_UINT) {
>  				rc = -1;
> -			} else if ((pKey2->flags & MEM_Int) != 0) {
> +			} else if (pKey2->type == MEM_TYPE_INT) {
>  				if (mem1.u.i < pKey2->u.i) {
>  					rc = -1;
>  				} else if (mem1.u.i > pKey2->u.i) {
>  					rc = +1;
>  				}
> -			} else if (pKey2->flags & MEM_Real) {
> +			} else if (pKey2->type == MEM_TYPE_DOUBLE) {
>  				rc = double_compare_nint64(pKey2->u.r, mem1.u.i,
>  							   -1);
> -			} else if ((pKey2->flags & MEM_Null) != 0) {
> +			} else if (pKey2->type == MEM_TYPE_NULL) {
>  				rc = 1;
> -			} else if ((pKey2->flags & MEM_Bool) != 0) {
> +			} else if (pKey2->type == MEM_TYPE_BOOL) {
>  				rc = 1;
>  			} else {
>  				rc = -1;
> @@ -2420,21 +2495,21 @@ sqlVdbeCompareMsgpack(const char **key1,
>  	case MP_DOUBLE:{
>  			mem1.u.r = mp_decode_double(&aKey1);
>   do_float:
> -			if ((pKey2->flags & MEM_Int) != 0) {
> +			if (pKey2->type == MEM_TYPE_INT) {
>  				rc = double_compare_nint64(mem1.u.r, pKey2->u.i,
>  							   1);
> -			} else if (pKey2->flags & MEM_UInt) {
> +			} else if (pKey2->type == MEM_TYPE_UINT) {
>  				rc = double_compare_uint64(mem1.u.r,
>  							   pKey2->u.u, 1);
> -			} else if (pKey2->flags & MEM_Real) {
> +			} else if (pKey2->type == MEM_TYPE_DOUBLE) {
>  				if (mem1.u.r < pKey2->u.r) {
>  					rc = -1;
>  				} else if (mem1.u.r > pKey2->u.r) {
>  					rc = +1;
>  				}
> -			} else if ((pKey2->flags & MEM_Null) != 0) {
> +			} else if (pKey2->type == MEM_TYPE_NULL) {
>  				rc = 1;
> -			} else if ((pKey2->flags & MEM_Bool) != 0) {
> +			} else if (pKey2->type == MEM_TYPE_BOOL) {
>  				rc = 1;
>  			} else {
>  				rc = -1;
> @@ -2442,7 +2517,7 @@ sqlVdbeCompareMsgpack(const char **key1,
>  			break;
>  		}
>  	case MP_STR:{
> -			if (pKey2->flags & MEM_Str) {
> +			if (pKey2->type == MEM_TYPE_STR) {
>  				struct key_def *key_def = unpacked->key_def;
>  				mem1.n = mp_decode_strl(&aKey1);
>  				mem1.z = (char *)aKey1;
> @@ -2450,14 +2525,15 @@ sqlVdbeCompareMsgpack(const char **key1,
>  				struct coll *coll =
>  					key_def->parts[key2_idx].coll;
>  				if (coll != NULL) {
> -					mem1.flags = MEM_Str;
> +					mem1.type = MEM_TYPE_STR;
> +					mem1.flags = 0;
>  					rc = vdbeCompareMemString(&mem1, pKey2,
>  								  coll);
>  				} else {
>  					goto do_bin_cmp;
>  				}
>  			} else {
> -				rc = (pKey2->flags & MEM_Blob) ? -1 : +1;
> +				rc = pKey2->type == MEM_TYPE_BIN ? -1 : +1;
>  			}
>  			break;
>  		}
> @@ -2466,7 +2542,7 @@ sqlVdbeCompareMsgpack(const char **key1,
>  			mem1.z = (char *)aKey1;
>  			aKey1 += mem1.n;
>   do_blob:
> -			if (pKey2->flags & MEM_Blob) {
> +			if (pKey2->type == MEM_TYPE_BIN) {
>  				if (pKey2->flags & MEM_Zero) {
>  					if (!isAllZero
>  					    ((const char *)mem1.z, mem1.n)) {
> @@ -2533,8 +2609,8 @@ mem_from_mp_ephemeral(struct Mem *mem, const char *buf, uint32_t *len)
>  		mem->z = (char *)buf;
>  		mp_next(&buf);
>  		mem->n = buf - mem->z;
> -		mem->flags = MEM_Blob | MEM_Ephem | MEM_Subtype;
> -		mem->subtype = SQL_SUBTYPE_MSGPACK;
> +		mem->type = MEM_TYPE_ARRAY;
> +		mem->flags = MEM_Ephem;
>  		mem->field_type = FIELD_TYPE_ARRAY;
>  		break;
>  	}
> @@ -2542,8 +2618,8 @@ mem_from_mp_ephemeral(struct Mem *mem, const char *buf, uint32_t *len)
>  		mem->z = (char *)buf;
>  		mp_next(&buf);
>  		mem->n = buf - mem->z;
> -		mem->flags = MEM_Blob | MEM_Ephem | MEM_Subtype;
> -		mem->subtype = SQL_SUBTYPE_MSGPACK;
> +		mem->type = MEM_TYPE_MAP;
> +		mem->flags = MEM_Ephem;
>  		mem->field_type = FIELD_TYPE_MAP;
>  		break;
>  	}
> @@ -2551,39 +2627,45 @@ mem_from_mp_ephemeral(struct Mem *mem, const char *buf, uint32_t *len)
>  		mem->z = (char *)buf;
>  		mp_next(&buf);
>  		mem->n = buf - mem->z;
> -		mem->flags = MEM_Blob | MEM_Ephem;
> +		mem->type = MEM_TYPE_BIN;
> +		mem->flags = MEM_Ephem;
>  		mem->field_type = FIELD_TYPE_VARBINARY;
>  		break;
>  	}
>  	case MP_NIL: {
>  		mp_decode_nil(&buf);
> -		mem->flags = MEM_Null;
> +		mem->type = MEM_TYPE_NULL;
> +		mem->flags = 0;
>  		mem->field_type = field_type_MAX;
>  		break;
>  	}
>  	case MP_BOOL: {
>  		mem->u.b = mp_decode_bool(&buf);
> -		mem->flags = MEM_Bool;
> +		mem->type = MEM_TYPE_BOOL;
> +		mem->flags = 0;
>  		mem->field_type = FIELD_TYPE_BOOLEAN;
>  		break;
>  	}
>  	case MP_UINT: {
>  		uint64_t v = mp_decode_uint(&buf);
>  		mem->u.u = v;
> -		mem->flags = MEM_UInt;
> +		mem->type = MEM_TYPE_UINT;
> +		mem->flags = 0;
>  		mem->field_type = FIELD_TYPE_INTEGER;
>  		break;
>  	}
>  	case MP_INT: {
>  		mem->u.i = mp_decode_int(&buf);
> -		mem->flags = MEM_Int;
> +		mem->type = MEM_TYPE_INT;
> +		mem->flags = 0;
>  		mem->field_type = FIELD_TYPE_INTEGER;
>  		break;
>  	}
>  	case MP_STR: {
>  		/* XXX u32->int */
>  		mem->n = (int) mp_decode_strl(&buf);
> -		mem->flags = MEM_Str | MEM_Ephem;
> +		mem->type = MEM_TYPE_STR;
> +		mem->flags = MEM_Ephem;
>  		mem->field_type = FIELD_TYPE_STRING;
>  install_blob:
>  		mem->z = (char *)buf;
> @@ -2593,17 +2675,20 @@ install_blob:
>  	case MP_BIN: {
>  		/* XXX u32->int */
>  		mem->n = (int) mp_decode_binl(&buf);
> -		mem->flags = MEM_Blob | MEM_Ephem;
> +		mem->type = MEM_TYPE_BIN;
> +		mem->flags = MEM_Ephem;
>  		mem->field_type = FIELD_TYPE_VARBINARY;
>  		goto install_blob;
>  	}
>  	case MP_FLOAT: {
>  		mem->u.r = mp_decode_float(&buf);
>  		if (sqlIsNaN(mem->u.r)) {
> -			mem->flags = MEM_Null;
> +			mem->type = MEM_TYPE_NULL;
> +			mem->flags = 0;
>  			mem->field_type = FIELD_TYPE_DOUBLE;
>  		} else {
> -			mem->flags = MEM_Real;
> +			mem->type = MEM_TYPE_DOUBLE;
> +			mem->flags = 0;
>  			mem->field_type = FIELD_TYPE_DOUBLE;
>  		}
>  		break;
> @@ -2611,10 +2696,12 @@ install_blob:
>  	case MP_DOUBLE: {
>  		mem->u.r = mp_decode_double(&buf);
>  		if (sqlIsNaN(mem->u.r)) {
> -			mem->flags = MEM_Null;
> +			mem->type = MEM_TYPE_NULL;
> +			mem->flags = 0;
>  			mem->field_type = FIELD_TYPE_DOUBLE;
>  		} else {
> -			mem->flags = MEM_Real;
> +			mem->type = MEM_TYPE_DOUBLE;
> +			mem->flags = 0;
>  			mem->field_type = FIELD_TYPE_DOUBLE;
>  		}
>  		break;
> @@ -2631,7 +2718,7 @@ mem_from_mp(struct Mem *mem, const char *buf, uint32_t *len)
>  {
>  	if (mem_from_mp_ephemeral(mem, buf, len) != 0)
>  		return -1;
> -	if ((mem->flags & (MEM_Str | MEM_Blob)) != 0) {
> +	if (mem_is_bytes(mem)) {
>  		assert((mem->flags & MEM_Ephem) != 0);
>  		if (sqlVdbeMemGrow(mem, mem->n, 1) != 0)
>  			return -1;
> @@ -2643,35 +2730,41 @@ void
>  mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var)
>  {
>  	assert(memIsValid(var));
> -	int64_t i;
> -	if (var->flags & MEM_Null) {
> +	switch (var->type) {
> +	case MEM_TYPE_NULL:
>  		mpstream_encode_nil(stream);
> -	} else if (var->flags & MEM_Real) {
> -		mpstream_encode_double(stream, var->u.r);
> -	} else if (var->flags & MEM_Int) {
> -		i = var->u.i;
> -		mpstream_encode_int(stream, i);
> -	} else if (var->flags & MEM_UInt) {
> -		i = var->u.u;
> -		mpstream_encode_uint(stream, i);
> -	} else if (var->flags & MEM_Str) {
> +		return;
> +	case MEM_TYPE_STR:
>  		mpstream_encode_strn(stream, var->z, var->n);
> -	} else if (var->flags & MEM_Bool) {
> -		mpstream_encode_bool(stream, var->u.b);
> -	} else {
> -		/*
> -		 * Emit BIN header iff the BLOB doesn't store
> -		 * MsgPack content.
> -		 */
> -		if (!mem_has_msgpack_subtype(var)) {
> -			uint32_t binl = var->n +
> -					((var->flags & MEM_Zero) ?
> -					var->u.nZero : 0);
> -			mpstream_encode_binl(stream, binl);
> +		return;
> +	case MEM_TYPE_INT:
> +		mpstream_encode_int(stream, var->u.i);
> +		return;
> +	case MEM_TYPE_UINT:
> +		mpstream_encode_uint(stream, var->u.u);
> +		return;
> +	case MEM_TYPE_DOUBLE:
> +		mpstream_encode_double(stream, var->u.r);
> +		return;
> +	case MEM_TYPE_BIN:
> +		if ((var->flags & MEM_Zero) != 0) {
> +			mpstream_encode_binl(stream, var->n + var->u.nZero);
> +			mpstream_memcpy(stream, var->z, var->n);
> +			mpstream_memset(stream, 0, var->u.nZero);
> +		} else {
> +			mpstream_encode_binl(stream, var->n);
> +			mpstream_memcpy(stream, var->z, var->n);
>  		}
> +		return;
> +	case MEM_TYPE_ARRAY:
> +	case MEM_TYPE_MAP:
>  		mpstream_memcpy(stream, var->z, var->n);
> -		if (var->flags & MEM_Zero)
> -			mpstream_memset(stream, 0, var->u.nZero);
> +		return;
> +	case MEM_TYPE_BOOL:
> +		mpstream_encode_bool(stream, var->u.b);
> +		return;
> +	default:
> +		unreachable();
>  	}
>  }
>  
> @@ -2734,24 +2827,26 @@ port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
>  	assert(is_flat == true);
>  	for (uint32_t i = 0; i < port->mem_count; i++) {
>  		struct Mem *mem = (struct Mem *)port->mem + i;
> -		switch (mem->flags & MEM_PURE_TYPE_MASK) {
> -		case MEM_Int:
> +		switch (mem->type) {
> +		case MEM_TYPE_INT:
>  			luaL_pushint64(L, mem->u.i);
>  			break;
> -		case MEM_UInt:
> +		case MEM_TYPE_UINT:
>  			luaL_pushuint64(L, mem->u.u);
>  			break;
> -		case MEM_Real:
> +		case MEM_TYPE_DOUBLE:
>  			lua_pushnumber(L, mem->u.r);
>  			break;
> -		case MEM_Str:
> -		case MEM_Blob:
> +		case MEM_TYPE_STR:
> +		case MEM_TYPE_BIN:
> +		case MEM_TYPE_MAP:
> +		case MEM_TYPE_ARRAY:
>  			lua_pushlstring(L, mem->z, mem->n);
>  			break;
> -		case MEM_Null:
> +		case MEM_TYPE_NULL:
>  			lua_pushnil(L);
>  			break;
> -		case MEM_Bool:
> +		case MEM_TYPE_BOOL:
>  			lua_pushboolean(L, mem->u.b);
>  			break;
>  		default:
> @@ -2844,23 +2939,28 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size)
>  		mem_clear(&val[i]);
>  		switch (field.type) {
>  		case MP_BOOL:
> -			val[i].flags = MEM_Bool;
> +			val[i].type = MEM_TYPE_BOOL;
> +			assert(val[i].flags == 0);
>  			val[i].u.b = field.bval;
>  			break;
>  		case MP_FLOAT:
> -			val[i].flags = MEM_Real;
> +			val[i].type = MEM_TYPE_DOUBLE;
> +			assert(val[i].flags == 0);
>  			val[i].u.r = field.fval;
>  			break;
>  		case MP_DOUBLE:
> -			val[i].flags = MEM_Real;
> +			val[i].type = MEM_TYPE_DOUBLE;
> +			assert(val[i].flags == 0);
>  			val[i].u.r = field.dval;
>  			break;
>  		case MP_INT:
> -			val[i].flags = MEM_Int;
> +			val[i].type = MEM_TYPE_INT;
> +			assert(val[i].flags == 0);
>  			val[i].u.i = field.ival;
>  			break;
>  		case MP_UINT:
> -			val[i].flags = MEM_UInt;
> +			val[i].type = MEM_TYPE_UINT;
> +			assert(val[i].flags == 0);
>  			val[i].u.i = field.ival;
>  			break;
>  		case MP_STR:
> @@ -2919,23 +3019,28 @@ port_c_get_vdbemem(struct port *base, uint32_t *size)
>  		const char *str;
>  		switch (mp_typeof(*data)) {
>  		case MP_BOOL:
> -			val[i].flags = MEM_Bool;
> +			val[i].type = MEM_TYPE_BOOL;
> +			assert(val[i].flags == 0);
>  			val[i].u.b = mp_decode_bool(&data);
>  			break;
>  		case MP_FLOAT:
> -			val[i].flags = MEM_Real;
> +			val[i].type = MEM_TYPE_DOUBLE;
> +			assert(val[i].flags == 0);
>  			val[i].u.r = mp_decode_float(&data);
>  			break;
>  		case MP_DOUBLE:
> -			val[i].flags = MEM_Real;
> +			val[i].type = MEM_TYPE_DOUBLE;
> +			assert(val[i].flags == 0);
>  			val[i].u.r = mp_decode_double(&data);
>  			break;
>  		case MP_INT:
> -			val[i].flags = MEM_Int;
> +			val[i].type = MEM_TYPE_INT;
> +			assert(val[i].flags == 0);
>  			val[i].u.i = mp_decode_int(&data);
>  			break;
>  		case MP_UINT:
> -			val[i].flags = MEM_UInt;
> +			val[i].type = MEM_TYPE_UINT;
> +			assert(val[i].flags == 0);
>  			val[i].u.u = mp_decode_uint(&data);
>  			break;
>  		case MP_STR:
> diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
> index 526b6bf3e..29d373cfd 100644
> --- a/src/box/sql/mem.h
> +++ b/src/box/sql/mem.h
> @@ -37,6 +37,23 @@ struct region;
>  struct mpstream;
>  struct VdbeFrame;
>  
> +enum mem_type {
> +	MEM_TYPE_NULL		= 1,
> +	MEM_TYPE_UINT		= 1 << 1,
> +	MEM_TYPE_INT		= 1 << 2,
> +	MEM_TYPE_STR		= 1 << 3,
> +	MEM_TYPE_BIN		= 1 << 4,
> +	MEM_TYPE_ARRAY		= 1 << 5,
> +	MEM_TYPE_MAP		= 1 << 6,
> +	MEM_TYPE_BOOL		= 1 << 7,
> +	MEM_TYPE_FLOAT		= 1 << 8,
> +	MEM_TYPE_DOUBLE		= 1 << 9,
> +	MEM_TYPE_INVALID	= 1 << 10,
> +	MEM_TYPE_FRAME		= 1 << 11,
> +	MEM_TYPE_PTR		= 1 << 12,
> +	MEM_TYPE_AGG		= 1 << 13,
> +};
> +
>  /*
>   * Internally, the vdbe manipulates nearly all SQL values as Mem
>   * structures. Each Mem struct may cache multiple representations (string,
> @@ -57,6 +74,8 @@ struct Mem {
>  		struct func *func;
>  		struct VdbeFrame *pFrame;	/* Used when flags==MEM_Frame */
>  	} u;
> +	/** Type of the value this MEM contains. */
> +	enum mem_type type;
>  	u32 flags;		/* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
>  	/** Subtype for this value. */
>  	enum sql_subtype subtype;
> @@ -81,29 +100,7 @@ struct Mem {
>  #endif
>  };
>  
> -/* One or more of the following flags are set to indicate the validOK
> - * representations of the value stored in the Mem struct.
> - *
> - * If the MEM_Null flag is set, then the value is an SQL NULL value.
> - * No other flags may be set in this case.
> - *
> - * If the MEM_Str flag is set then Mem.z points at a string representation.
> - * Usually this is encoded in the same unicode encoding as the main
> - * database (see below for exceptions). If the MEM_Term flag is also
> - * set, then the string is nul terminated. The MEM_Int and MEM_Real
> - * flags may coexist with the MEM_Str flag.
> - */
> -#define MEM_Null      0x0001	/* Value is NULL */
> -#define MEM_Str       0x0002	/* Value is a string */
> -#define MEM_Int       0x0004	/* Value is an integer */
> -#define MEM_Real      0x0008	/* Value is a real number */
> -#define MEM_Blob      0x0010	/* Value is a BLOB */
> -#define MEM_Bool      0x0020    /* Value is a bool */
> -#define MEM_UInt      0x0040	/* Value is an unsigned integer */
> -#define MEM_Frame     0x0080	/* Value is a VdbeFrame object */
> -#define MEM_Undefined 0x0100	/* Value is undefined */
>  #define MEM_Cleared   0x0200	/* NULL set by OP_Null, not from data */
> -#define MEM_TypeMask  0x83ff	/* Mask of type bits */
>  
>  /* Whenever Mem contains a valid string or blob representation, one of
>   * the following flags must be set to determine the memory management
> @@ -114,175 +111,151 @@ struct Mem {
>  #define MEM_Dyn       0x0800	/* Need to call Mem.xDel() on Mem.z */
>  #define MEM_Static    0x1000	/* Mem.z points to a static string */
>  #define MEM_Ephem     0x2000	/* Mem.z points to an ephemeral string */
> -#define MEM_Agg       0x4000	/* Mem.z points to an agg function context */
>  #define MEM_Zero      0x8000	/* Mem.i contains count of 0s appended to blob */
> -#define MEM_Subtype   0x10000	/* Mem.eSubtype is valid */
> -#define MEM_Ptr       0x20000	/* Value is a generic pointer */
> -
> -/**
> - * In contrast to Mem_TypeMask, this one allows to get
> - * pure type of memory cell, i.e. without _Dyn/_Zero and other
> - * auxiliary flags.
> - */
> -enum {
> -	MEM_PURE_TYPE_MASK = 0x7f
> -};
> -
> -static_assert(MEM_PURE_TYPE_MASK == (MEM_Null | MEM_Str | MEM_Int | MEM_Real |
> -				     MEM_Blob | MEM_Bool | MEM_UInt),
> -	      "value of type mask must consist of corresponding to memory "\
> -	      "type bits");
>  
>  static inline bool
>  mem_is_null(const struct Mem *mem)
>  {
> -	return (mem->flags & MEM_Null) != 0;
> +	return mem->type == MEM_TYPE_NULL;
>  }
>  
>  static inline bool
>  mem_is_uint(const struct Mem *mem)
>  {
> -	return (mem->flags & MEM_UInt) != 0;
> +	return mem->type == MEM_TYPE_UINT;
>  }
>  
>  static inline bool
>  mem_is_nint(const struct Mem *mem)
>  {
> -	return (mem->flags & MEM_Int) != 0;
> +	return mem->type == MEM_TYPE_INT;
>  }
>  
>  static inline bool
>  mem_is_str(const struct Mem *mem)
>  {
> -	return (mem->flags & MEM_Str) != 0;
> +	return mem->type == MEM_TYPE_STR;
>  }
>  
>  static inline bool
>  mem_is_num(const struct Mem *mem)
>  {
> -	return (mem->flags & (MEM_Real | MEM_Int | MEM_UInt)) != 0;
> +	enum mem_type type = mem->type;
> +	return (type & (MEM_TYPE_UINT | MEM_TYPE_INT | MEM_TYPE_DOUBLE)) != 0;
>  }
>  
>  static inline bool
>  mem_is_double(const struct Mem *mem)
>  {
> -	return (mem->flags & MEM_Real) != 0;
> +	return mem->type == MEM_TYPE_DOUBLE;
>  }
>  
>  static inline bool
>  mem_is_int(const struct Mem *mem)
>  {
> -	return (mem->flags & (MEM_Int | MEM_UInt)) != 0;
> +	return (mem->type & (MEM_TYPE_UINT | MEM_TYPE_INT)) != 0;
>  }
>  
>  static inline bool
>  mem_is_bool(const struct Mem *mem)
>  {
> -	return (mem->flags & MEM_Bool) != 0;
> +	return mem->type == MEM_TYPE_BOOL;
>  }
>  
>  static inline bool
>  mem_is_bin(const struct Mem *mem)
>  {
> -	return (mem->flags & MEM_Blob) != 0 && (mem->flags & MEM_Subtype) == 0;
> +	return mem->type == MEM_TYPE_BIN;
>  }
>  
>  static inline bool
>  mem_is_map(const struct Mem *mem)
>  {
> -	assert((mem->flags & MEM_Subtype) == 0 || (mem->flags & MEM_Blob) != 0);
> -	assert((mem->flags & MEM_Subtype) == 0 ||
> -	       mem->subtype == SQL_SUBTYPE_MSGPACK);
> -	return (mem->flags & MEM_Subtype) != 0 && mp_typeof(*mem->z) == MP_MAP;
> +	return mem->type == MEM_TYPE_MAP;
>  }
>  
>  static inline bool
>  mem_is_array(const struct Mem *mem)
>  {
> -	assert((mem->flags & MEM_Subtype) == 0 || (mem->flags & MEM_Blob) != 0);
> -	assert((mem->flags & MEM_Subtype) == 0 ||
> -	       mem->subtype == SQL_SUBTYPE_MSGPACK);
> -	return (mem->flags & MEM_Subtype) != 0 &&
> -	       mp_typeof(*mem->z) == MP_ARRAY;
> +	return mem->type == MEM_TYPE_ARRAY;
>  }
>  
>  static inline bool
>  mem_is_agg(const struct Mem *mem)
>  {
> -	return (mem->flags & MEM_Agg) != 0;
> +	return mem->type == MEM_TYPE_AGG;
>  }
>  
>  static inline bool
>  mem_is_bytes(const struct Mem *mem)
>  {
> -	return (mem->flags & (MEM_Blob | MEM_Str)) != 0;
> +	return (mem->type & (MEM_TYPE_BIN | MEM_TYPE_STR |
> +			     MEM_TYPE_MAP | MEM_TYPE_ARRAY)) != 0;
>  }
>  
>  static inline bool
>  mem_is_frame(const struct Mem *mem)
>  {
> -	return (mem->flags & MEM_Frame) != 0;
> +	return mem->type == MEM_TYPE_FRAME;
>  }
>  
>  static inline bool
>  mem_is_invalid(const struct Mem *mem)
>  {
> -	return (mem->flags & MEM_Undefined) != 0;
> +	return mem->type == MEM_TYPE_INVALID;
>  }
>  
>  static inline bool
>  mem_is_static(const struct Mem *mem)
>  {
> -	assert((mem->flags & (MEM_Str | MEM_Blob)) != 0);
> +	assert(mem_is_bytes(mem));
>  	return (mem->flags & MEM_Static) != 0;
>  }
>  
>  static inline bool
>  mem_is_ephemeral(const struct Mem *mem)
>  {
> -	assert((mem->flags & (MEM_Str | MEM_Blob)) != 0);
> +	assert(mem_is_bytes(mem));
>  	return (mem->flags & MEM_Ephem) != 0;
>  }
>  
>  static inline bool
>  mem_is_dynamic(const struct Mem *mem)
>  {
> -	assert((mem->flags & (MEM_Str | MEM_Blob)) != 0);
> +	assert(mem_is_bytes(mem));
>  	return (mem->flags & MEM_Dyn) != 0;
>  }
>  
>  static inline bool
>  mem_is_allocated(const struct Mem *mem)
>  {
> -	return (mem->flags & (MEM_Str | MEM_Blob)) != 0 &&
> -	       mem->z == mem->zMalloc;
> +	return mem_is_bytes(mem) && mem->z == mem->zMalloc;
>  }
>  
>  static inline bool
>  mem_is_cleared(const struct Mem *mem)
>  {
> -	assert((mem->flags & MEM_Cleared) == 0 || (mem->flags & MEM_Null) != 0);
> +	assert((mem->flags & MEM_Cleared) == 0 || mem->type == MEM_TYPE_NULL);
>  	return (mem->flags & MEM_Cleared) != 0;
>  }
>  
>  static inline bool
>  mem_is_zerobin(const struct Mem *mem)
>  {
> -	assert((mem->flags & MEM_Zero) == 0 || (mem->flags & MEM_Blob) != 0);
> +	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
>  	return (mem->flags & MEM_Zero) != 0;
>  }
>  
>  static inline bool
>  mem_is_same_type(const struct Mem *mem1, const struct Mem *mem2)
>  {
> -	return (mem1->flags & MEM_PURE_TYPE_MASK) ==
> -	       (mem2->flags & MEM_PURE_TYPE_MASK);
> +	return mem1->type == mem2->type;
>  }
>  
>  static inline bool
>  mem_is_any_null(const struct Mem *mem1, const struct Mem *mem2)
>  {
> -	return ((mem1->flags | mem2->flags) & MEM_Null) != 0;
> +	return ((mem1->type| mem2->type) & MEM_TYPE_NULL) != 0;
>  }
>  
>  /**
> @@ -943,7 +916,7 @@ registerTrace(int iReg, Mem *p);
>   * Return true if a memory cell is not marked as invalid.  This macro
>   * is for use inside assert() statements only.
>   */
> -#define memIsValid(M)  ((M)->flags & MEM_Undefined)==0
> +#define memIsValid(M)  ((M)->type != MEM_TYPE_INVALID)
>  #endif
>  
>  int sqlVdbeMemExpandBlob(struct Mem *);
> @@ -969,9 +942,8 @@ int sqlVdbeMemTooBig(Mem *);
>  /* Return TRUE if Mem X contains dynamically allocated content - anything
>   * that needs to be deallocated to avoid a leak.
>   */
> -#define VdbeMemDynamic(X)  \
> -  (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_Frame))!=0)
> -
> +#define VdbeMemDynamic(X) (((X)->flags & MEM_Dyn) != 0 ||\
> +			   ((X)->type & (MEM_TYPE_AGG | MEM_TYPE_FRAME)) != 0)
>  
>  int sqlMemCompare(const Mem *, const Mem *, const struct coll *);
>  
> diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
> index ba5c08a00..1aa201466 100644
> --- a/src/box/sql/vdbemem.c
> +++ b/src/box/sql/vdbemem.c
> @@ -93,7 +93,8 @@ valueNew(sql * db, struct ValueNewStat4Ctx *p)
>  			pRec->aMem = (Mem *)((char *) pRec +
>  					     ROUND8(sizeof(UnpackedRecord)));
>  			for (uint32_t i = 0; i < part_count; i++) {
> -				pRec->aMem[i].flags = MEM_Null;
> +				pRec->aMem[i].type = MEM_NULL;
> +				pRec->aMem[i].flags = 0;
>  				pRec->aMem[i].db = db;
>  			}
>  			p->ppRec[0] = pRec;

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

* Re: [Tarantool-patches] [PATCH v2 3/3] sql: replace MEM-type flags by enum mem_type
  2021-05-17 12:18     ` Mergen Imeev via Tarantool-patches
  2021-05-17 12:34       ` Mergen Imeev via Tarantool-patches
@ 2021-05-21 18:59       ` Vladislav Shpilevoy via Tarantool-patches
  2021-05-24 10:56         ` Mergen Imeev via Tarantool-patches
  1 sibling, 1 reply; 11+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-05-21 18:59 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

Hi! Thanks for the fixes!

See 2 small comments below.

> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index b6ff6397f..f7788021d 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -1155,11 +1214,11 @@ mem_get_str0(const struct Mem *mem, const char **s)
>  int
>  mem_get_bin(const struct Mem *mem, const char **s)
>  {
> -	if ((mem->flags & MEM_Str) != 0) {
> +	if (mem->type == MEM_TYPE_STR) {
>  		*s = mem->n > 0 ? mem->z : NULL;
>  		return 0;
>  	}
> -	if ((mem->flags & MEM_Blob) == 0 || (mem->flags & MEM_Zero) != 0)
> +	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
>  		return -1;

1. AFAIR, it is not possible to have MEM_Zero for non-bin. Which makes
the second condition unreachable, right? The same in mem_len().

>  	*s = mem->z;
>  	return 0;
> @@ -1644,14 +1717,15 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
>  		return -1;
>  	}
>  	result->u.i = ~i;
> -	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
> +	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
> +	assert(result->flags == 0);
>  	return 0;
>  }
>  
>  int
>  mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
>  {
> -	if ((a->flags & b->flags & MEM_Bool) == 0)
> +	if (a->type != MEM_TYPE_BOOL || b->type != MEM_TYPE_BOOL)

2. You could leave the old way so as to avoid || (branching).

>  		return -1;
>  	if (a->u.b == b->u.b)
>  		*result = 0;

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

* Re: [Tarantool-patches] [PATCH v2 3/3] sql: replace MEM-type flags by enum mem_type
  2021-05-21 18:59       ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-05-24 10:56         ` Mergen Imeev via Tarantool-patches
  2021-05-24 15:26           ` Vladislav Shpilevoy via Tarantool-patches
  0 siblings, 1 reply; 11+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-05-24 10:56 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thank you for the review! My answers and diff below.

On Fri, May 21, 2021 at 08:59:14PM +0200, Vladislav Shpilevoy wrote:
> Hi! Thanks for the fixes!
> 
> See 2 small comments below.
> 
> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index b6ff6397f..f7788021d 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -1155,11 +1214,11 @@ mem_get_str0(const struct Mem *mem, const char **s)
> >  int
> >  mem_get_bin(const struct Mem *mem, const char **s)
> >  {
> > -	if ((mem->flags & MEM_Str) != 0) {
> > +	if (mem->type == MEM_TYPE_STR) {
> >  		*s = mem->n > 0 ? mem->z : NULL;
> >  		return 0;
> >  	}
> > -	if ((mem->flags & MEM_Blob) == 0 || (mem->flags & MEM_Zero) != 0)
> > +	if (mem->type != MEM_TYPE_BIN || (mem->flags & MEM_Zero) != 0)
> >  		return -1;
> 
> 1. AFAIR, it is not possible to have MEM_Zero for non-bin. Which makes
> the second condition unreachable, right? The same in mem_len().
> 
It is true that it should be not possible to have MEM_Zero flag for
non-VARBINARY value. However, here it is checked that the value is VARBINARY
and if it is, the flag is checked. I think there is nothing wrong here.

Still, In mem_len() I removed one condition and added assert instead.

> >  	*s = mem->z;
> >  	return 0;
> > @@ -1644,14 +1717,15 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
> >  		return -1;
> >  	}
> >  	result->u.i = ~i;
> > -	result->flags = result->u.i < 0 ? MEM_Int : MEM_UInt;
> > +	result->type = result->u.i < 0 ? MEM_TYPE_INT : MEM_TYPE_UINT;
> > +	assert(result->flags == 0);
> >  	return 0;
> >  }
> >  
> >  int
> >  mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
> >  {
> > -	if ((a->flags & b->flags & MEM_Bool) == 0)
> > +	if (a->type != MEM_TYPE_BOOL || b->type != MEM_TYPE_BOOL)
> 
> 2. You could leave the old way so as to avoid || (branching).
> 
Thanks, fixed. Also fixed in one more place.

> >  		return -1;
> >  	if (a->u.b == b->u.b)
> >  		*result = 0;


Diff:

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index f7788021d..1bd70c37f 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1229,7 +1229,8 @@ mem_len(const struct Mem *mem, uint32_t *len)
 {
 	if (!mem_is_bytes(mem))
 		return -1;
-	if (mem->type == MEM_TYPE_BIN && (mem->flags & MEM_Zero) != 0)
+	assert((mem->flags & MEM_Zero) == 0 || mem->type == MEM_TYPE_BIN);
+	if ((mem->flags & MEM_Zero) != 0)
 		*len = mem->n + mem->u.nZero;
 	else
 		*len = mem->n;
@@ -1725,7 +1726,7 @@ mem_bit_not(const struct Mem *mem, struct Mem *result)
 int
 mem_cmp_bool(const struct Mem *a, const struct Mem *b, int *result)
 {
-	if (a->type != MEM_TYPE_BOOL || b->type != MEM_TYPE_BOOL)
+	if ((a->type & b->type & MEM_TYPE_BOOL) == 0)
 		return -1;
 	if (a->u.b == b->u.b)
 		*result = 0;
@@ -2345,7 +2346,7 @@ sqlMemCompare(const Mem * pMem1, const Mem * pMem2, const struct coll * pColl)
 		       (int)(type1 == MEM_TYPE_NULL);
 
 	if (((type1 | type2) & MEM_TYPE_BOOL) != 0) {
-		if (type1 == MEM_TYPE_BOOL && type2 == MEM_TYPE_BOOL) {
+		if ((type1 & type2 & MEM_TYPE_BOOL) != 0) {
 			if (pMem1->u.b == pMem2->u.b)
 				return 0;
 			if (pMem1->u.b)

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

* Re: [Tarantool-patches] [PATCH v2 3/3] sql: replace MEM-type flags by enum mem_type
  2021-05-24 10:56         ` Mergen Imeev via Tarantool-patches
@ 2021-05-24 15:26           ` Vladislav Shpilevoy via Tarantool-patches
  2021-05-25 11:13             ` Mergen Imeev via Tarantool-patches
  0 siblings, 1 reply; 11+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-05-24 15:26 UTC (permalink / raw)
  To: Mergen Imeev; +Cc: tarantool-patches

Hi! Thanks for the fixes!

See 4 new comments below, somehow I didn't notice them first time.

> diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> index b6ff6397f..1bd70c37f 100644
> --- a/src/box/sql/mem.c
> +++ b/src/box/sql/mem.c
> @@ -1236,7 +1297,8 @@ mem_move(struct Mem *to, struct Mem *from)
>  {
>  	mem_destroy(to);
>  	memcpy(to, from, sizeof(*to));
> -	from->flags = MEM_Null;
> +	from->type = MEM_TYPE_NULL;
> +	from->flags = 0;

1. Flags are already 0 after mem_destroy().

>  	from->szMalloc = 0;
>  	from->zMalloc = NULL;
>  }
> @@ -1295,8 +1357,9 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
>  	if (sqlVdbeMemGrow(result, size, result == a) != 0)
>  		return -1;
>  
> -	result->flags = a->flags & (MEM_Str | MEM_Blob);
> -	if ((result->flags & MEM_Blob) != 0)
> +	result->type = a->type == MEM_TYPE_STR ? MEM_TYPE_STR : MEM_TYPE_BIN;

2. You could also make `result->type = a->type;`. Without `?:`. A->type
is anyway either STR or BIN.

> +	result->flags = 0;
> +	if (result->type == MEM_TYPE_BIN)
>  		result->field_type = FIELD_TYPE_VARBINARY;
>  	if (result != a)
> @@ -2322,12 +2395,13 @@ sql_vdbemem_finalize(struct Mem *mem, struct func *func)
>  	assert(func != NULL);
>  	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
>  	assert(func->def->aggregate == FUNC_AGGREGATE_GROUP);
> -	assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
> +	assert(mem->type == MEM_TYPE_NULL || func == mem->u.func);
>  	sql_context ctx;
>  	memset(&ctx, 0, sizeof(ctx));
>  	Mem t;
>  	memset(&t, 0, sizeof(t));
> -	t.flags = MEM_Null;
> +	t.type = MEM_TYPE_NULL;
> +	t.flags = 0;

3. It is already 0 after memset() above.

>  	t.db = mem->db;
>  	t.field_type = field_type_MAX;
>  	ctx.pOut = &t;
> diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
> index ba5c08a00..1aa201466 100644
> --- a/src/box/sql/vdbemem.c
> +++ b/src/box/sql/vdbemem.c
> @@ -93,7 +93,8 @@ valueNew(sql * db, struct ValueNewStat4Ctx *p)
>  			pRec->aMem = (Mem *)((char *) pRec +
>  					     ROUND8(sizeof(UnpackedRecord)));
>  			for (uint32_t i = 0; i < part_count; i++) {
> -				pRec->aMem[i].flags = MEM_Null;
> +				pRec->aMem[i].type = MEM_NULL;
> +				pRec->aMem[i].flags = 0;

4. Flags are already 0 - the memory was allocated using sqlDbMallocZero().

>  				pRec->aMem[i].db = db;
>  			}
>  			p->ppRec[0] = pRec;


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

* Re: [Tarantool-patches] [PATCH v2 3/3] sql: replace MEM-type flags by enum mem_type
  2021-05-24 15:26           ` Vladislav Shpilevoy via Tarantool-patches
@ 2021-05-25 11:13             ` Mergen Imeev via Tarantool-patches
  0 siblings, 0 replies; 11+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-05-25 11:13 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thank you for the review! My answers and diff below.

On Mon, May 24, 2021 at 05:26:22PM +0200, Vladislav Shpilevoy wrote:
> Hi! Thanks for the fixes!
> 
> See 4 new comments below, somehow I didn't notice them first time.
> 
> > diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
> > index b6ff6397f..1bd70c37f 100644
> > --- a/src/box/sql/mem.c
> > +++ b/src/box/sql/mem.c
> > @@ -1236,7 +1297,8 @@ mem_move(struct Mem *to, struct Mem *from)
> >  {
> >  	mem_destroy(to);
> >  	memcpy(to, from, sizeof(*to));
> > -	from->flags = MEM_Null;
> > +	from->type = MEM_TYPE_NULL;
> > +	from->flags = 0;
> 
> 1. Flags are already 0 after mem_destroy().
> 
No, mem_destroy() is called for MEM "to".

> >  	from->szMalloc = 0;
> >  	from->zMalloc = NULL;
> >  }
> > @@ -1295,8 +1357,9 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
> >  	if (sqlVdbeMemGrow(result, size, result == a) != 0)
> >  		return -1;
> >  
> > -	result->flags = a->flags & (MEM_Str | MEM_Blob);
> > -	if ((result->flags & MEM_Blob) != 0)
> > +	result->type = a->type == MEM_TYPE_STR ? MEM_TYPE_STR : MEM_TYPE_BIN;
> 
> 2. You could also make `result->type = a->type;`. Without `?:`. A->type
> is anyway either STR or BIN.
> 
Fixed.

> > +	result->flags = 0;
> > +	if (result->type == MEM_TYPE_BIN)
> >  		result->field_type = FIELD_TYPE_VARBINARY;
> >  	if (result != a)
> > @@ -2322,12 +2395,13 @@ sql_vdbemem_finalize(struct Mem *mem, struct func *func)
> >  	assert(func != NULL);
> >  	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
> >  	assert(func->def->aggregate == FUNC_AGGREGATE_GROUP);
> > -	assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
> > +	assert(mem->type == MEM_TYPE_NULL || func == mem->u.func);
> >  	sql_context ctx;
> >  	memset(&ctx, 0, sizeof(ctx));
> >  	Mem t;
> >  	memset(&t, 0, sizeof(t));
> > -	t.flags = MEM_Null;
> > +	t.type = MEM_TYPE_NULL;
> > +	t.flags = 0;
> 
> 3. It is already 0 after memset() above.
> 
Fixed.

> >  	t.db = mem->db;
> >  	t.field_type = field_type_MAX;
> >  	ctx.pOut = &t;
> > diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
> > index ba5c08a00..1aa201466 100644
> > --- a/src/box/sql/vdbemem.c
> > +++ b/src/box/sql/vdbemem.c
> > @@ -93,7 +93,8 @@ valueNew(sql * db, struct ValueNewStat4Ctx *p)
> >  			pRec->aMem = (Mem *)((char *) pRec +
> >  					     ROUND8(sizeof(UnpackedRecord)));
> >  			for (uint32_t i = 0; i < part_count; i++) {
> > -				pRec->aMem[i].flags = MEM_Null;
> > +				pRec->aMem[i].type = MEM_NULL;
> > +				pRec->aMem[i].flags = 0;
> 
> 4. Flags are already 0 - the memory was allocated using sqlDbMallocZero().
> 
Fixed.

> >  				pRec->aMem[i].db = db;
> >  			}
> >  			p->ppRec[0] = pRec;
> 


Diff:

diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c
index 1bd70c37f..03fbffc7f 100644
--- a/src/box/sql/mem.c
+++ b/src/box/sql/mem.c
@@ -1357,7 +1357,7 @@ mem_concat(struct Mem *a, struct Mem *b, struct Mem *result)
 	if (sqlVdbeMemGrow(result, size, result == a) != 0)
 		return -1;
 
-	result->type = a->type == MEM_TYPE_STR ? MEM_TYPE_STR : MEM_TYPE_BIN;
+	result->type = a->type;
 	result->flags = 0;
 	if (result->type == MEM_TYPE_BIN)
 		result->field_type = FIELD_TYPE_VARBINARY;
@@ -2401,7 +2401,7 @@ sql_vdbemem_finalize(struct Mem *mem, struct func *func)
 	Mem t;
 	memset(&t, 0, sizeof(t));
 	t.type = MEM_TYPE_NULL;
-	t.flags = 0;
+	assert(t.flags == 0);
 	t.db = mem->db;
 	t.field_type = field_type_MAX;
 	ctx.pOut = &t;
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 1aa201466..2c5099616 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -94,7 +94,7 @@ valueNew(sql * db, struct ValueNewStat4Ctx *p)
 					     ROUND8(sizeof(UnpackedRecord)));
 			for (uint32_t i = 0; i < part_count; i++) {
 				pRec->aMem[i].type = MEM_NULL;
-				pRec->aMem[i].flags = 0;
+				assert(pRec->aMem[i].flags == 0);
 				pRec->aMem[i].db = db;
 			}
 			p->ppRec[0] = pRec;

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

* Re: [Tarantool-patches] [PATCH v2 0/3] Replace MEM-type flags by enum mem_type
  2021-04-27 16:55 [Tarantool-patches] [PATCH v2 0/3] Replace MEM-type flags by enum mem_type Mergen Imeev via Tarantool-patches
  2021-04-27 16:55 ` [Tarantool-patches] [PATCH v2 3/3] sql: replace " Mergen Imeev via Tarantool-patches
@ 2021-05-25 21:33 ` Vladislav Shpilevoy via Tarantool-patches
  2021-05-26  8:06 ` Kirill Yukhin via Tarantool-patches
  2 siblings, 0 replies; 11+ messages in thread
From: Vladislav Shpilevoy via Tarantool-patches @ 2021-05-25 21:33 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

Hi! Thanks for the patchset!

LGTM.

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

* Re: [Tarantool-patches] [PATCH v2 0/3] Replace MEM-type flags by enum mem_type
  2021-04-27 16:55 [Tarantool-patches] [PATCH v2 0/3] Replace MEM-type flags by enum mem_type Mergen Imeev via Tarantool-patches
  2021-04-27 16:55 ` [Tarantool-patches] [PATCH v2 3/3] sql: replace " Mergen Imeev via Tarantool-patches
  2021-05-25 21:33 ` [Tarantool-patches] [PATCH v2 0/3] Replace " Vladislav Shpilevoy via Tarantool-patches
@ 2021-05-26  8:06 ` Kirill Yukhin via Tarantool-patches
  2 siblings, 0 replies; 11+ messages in thread
From: Kirill Yukhin via Tarantool-patches @ 2021-05-26  8:06 UTC (permalink / raw)
  To: imeevma; +Cc: v.shpilevoy, tarantool-patches

Hello,

On 27 апр 19:55, Mergen Imeev via Tarantool-patches wrote:
> After this patche-set, the MEM type could be determined by the value in the
> special field, and not by the "flags" field.
> 
> https://github.com/tarantool/tarantool/issues/4906
> https://github.com/tarantool/tarantool/tree/imeevma/gh-4906-rework-mem-types
> 
> Changes in v2:
>  - Added two new commits.

I've checked your patch into master.

--
Regards, Kirill Yukhin

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

end of thread, other threads:[~2021-05-26  8:06 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-27 16:55 [Tarantool-patches] [PATCH v2 0/3] Replace MEM-type flags by enum mem_type Mergen Imeev via Tarantool-patches
2021-04-27 16:55 ` [Tarantool-patches] [PATCH v2 3/3] sql: replace " Mergen Imeev via Tarantool-patches
2021-04-29 21:09   ` Vladislav Shpilevoy via Tarantool-patches
2021-05-17 12:18     ` Mergen Imeev via Tarantool-patches
2021-05-17 12:34       ` Mergen Imeev via Tarantool-patches
2021-05-21 18:59       ` Vladislav Shpilevoy via Tarantool-patches
2021-05-24 10:56         ` Mergen Imeev via Tarantool-patches
2021-05-24 15:26           ` Vladislav Shpilevoy via Tarantool-patches
2021-05-25 11:13             ` Mergen Imeev via Tarantool-patches
2021-05-25 21:33 ` [Tarantool-patches] [PATCH v2 0/3] Replace " Vladislav Shpilevoy via Tarantool-patches
2021-05-26  8:06 ` Kirill Yukhin via Tarantool-patches

Tarantool development patches archive

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://lists.tarantool.org/tarantool-patches/0 tarantool-patches/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 tarantool-patches tarantool-patches/ https://lists.tarantool.org/tarantool-patches \
		tarantool-patches@dev.tarantool.org.
	public-inbox-index tarantool-patches

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git