Tarantool development patches archive
 help / color / mirror / Atom feed
From: Mergen Imeev via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Cc: tarantool-patches@dev.tarantool.org
Subject: Re: [Tarantool-patches] [PATCH v2 2/3] sql: make mem_is_bin() to check only for VARBINARY
Date: Mon, 17 May 2021 15:05:22 +0300	[thread overview]
Message-ID: <20210517120522.GB177059@tarantool.org> (raw)
In-Reply-To: <e58c3fcf-1ded-f92f-5a37-1628dac593e8@tarantool.org>

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

On Thu, Apr 29, 2021 at 11:05:19PM +0200, Vladislav Shpilevoy wrote:
> Thanks for working on this!
> 
> See 11 comments below!
> 
> On 27.04.2021 18:55, Mergen Imeev via Tarantool-patches wrote:
> > After this patch, the mem_is_bin() function will return 'true' only if
> > the value that the MEM contains is of type VARBINARY. This patch also
> > adds the mem_is_bin_ext() function, which is used to check if a MEM
> > contains value of type VARBINARY or value of types that are currently
> > considered VARBINARY extensions - MAP and ARRAY.
> > 
> > Part of #4906
> > ---
> >  src/box/sql/func.c | 10 +++++-----
> >  src/box/sql/mem.h  |  8 +++++++-
> >  src/box/sql/vdbe.c | 16 ++++++++--------
> >  3 files changed, 20 insertions(+), 14 deletions(-)
> > 
> > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > index d282b2cea..bed7e8488 100644
> > --- a/src/box/sql/func.c
> > +++ b/src/box/sql/func.c
> > @@ -553,7 +553,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
> >  	}
> >  	if (mem_is_null(argv[0]))
> >  		return;
> > -	if (mem_is_bin(argv[0])) {
> > +	if (mem_is_bin_ext(argv[0])) {
> 
> 1. The name is quite confusing. I spent some time thinking about a
> better one, but can't find any. Maybe because such a function looks
> very strange in its purpose, as well as some of its usages.
> 
> Maybe better add a function mem_is_nested() to check for MP_ARRAY
> and MP_MAP. Or mem_is_scalar(), which checks it is not MP_ARRAY and
> not MP_MAP and you call it with negation. Or add special checks
> for array and map. mem_is_bin_ext() would be either
> 
> 	mem_is_bin() || mem_is_nested()
> 
> or
> 
> 	mem_is_bin() || !mem_is_scalar()
> 
> or
> 
> 	mem_is_bin() || mem_is_array() || mem_is_map()
> 
Fixed. However, it turned out that you suggestion made diff even less, though
you suggested to use two or three functions instead of one.

> Then you would see something strange below.
> 
> Why in ROUND() we check for types we do not accept instead of
> checking the types we do accept? I mean, why not
> 
> 	if (!mem_is_number() && !mem_is_str())
> 		return error;
> 
> instead of
> 
> 	if (mem_is_bin_ext())
> 		return error;
> 
> What will happen when you will add UUID, date? Will you change
> this on each new type like that?:
> 
> 	if (mem_is_bin_ext() || mem_is_uuid() || mem_is_date() || ...)
> 		return error;
> 
> IMO would be easier to maintain a list of supported types than a
> list of not supported types. Worth changing now?
> 
I changed here, however I do not think that it should be done this way, since
we have to take into account our implicit cast rules and change this code every
time we will add something, that can be converted to value of accepted type.
Instead, all check and convertions should be done before the function is called.
This will be done in issue #4159, which was already partially done last year.
Last time I wasn't able to complete this issue, but we were able to come to a
conclusion about how should it be done. I think I will return to the issue in
Q3 or Q4.

> >  		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
> >  			 mem_str(argv[0]), "numeric");
> >  		context->is_aborted = true;
> > @@ -613,7 +613,7 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
> >  	const char *z2;                                                        \
> >  	int n;                                                                 \
> >  	UNUSED_PARAMETER(argc);                                                \
> > -	if (mem_is_bin(argv[0])) {                                             \
> > +	if (mem_is_bin_ext(argv[0])) {                                         \
> 
> 2. Ditto.
> 
Fixed.

> >  		diag_set(ClientError, ER_INCONSISTENT_TYPES, "text",           \
> >  			 "varbinary");                                         \
> >  		context->is_aborted = true;                                    \
> > @@ -694,7 +694,7 @@ randomBlob(sql_context * context, int argc, sql_value ** argv)
> >  	unsigned char *p;
> >  	assert(argc == 1);
> >  	UNUSED_PARAMETER(argc);
> > -	if (mem_is_bin(argv[0])) {
> > +	if (mem_is_bin_ext(argv[0])) {
> 
> 3. Ditto.
> 
Fixed.

> >  		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
> >  			 mem_str(argv[0]), "numeric");
> >  		context->is_aborted = true;
> > @@ -1455,7 +1455,7 @@ trim_func_one_arg(struct sql_context *context, sql_value *arg)
> >  	const unsigned char *default_trim;
> >  	if (mem_is_null(arg))
> >  		return;
> > -	if (mem_is_bin(arg))
> > +	if (mem_is_bin_ext(arg))
> 
> 4. TBH, I would ban MP_ARRAY and MP_MAP from TRIM(). It makes 0
> sense for them. I am not sure it is even tested. So we are not
> talking about a notable 'behaviour change' really.
> 
In current design TRIM() accepts any argument. I reverted this change, however
it should not affect MAP and ARRAY values.

> >  		default_trim = (const unsigned char *) "\0";
> >  	else
> >  		default_trim = (const unsigned char *) " ";
> > @@ -1584,7 +1584,7 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
> >  		1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
> >  	};
> >  	assert(argc == 1);
> > -	if (mem_is_bin(argv[0])) {
> > +	if (mem_is_bin_ext(argv[0])) {
> 
> 5. Ditto.
> 
Fixed.

> > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> > index 2308587e7..bedfa87af 100644
> > --- a/src/box/sql/vdbe.c
> > +++ b/src/box/sql/vdbe.c
> > @@ -1634,9 +1634,9 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
> >  				 "boolean");
> >  			goto abort_due_to_error;
> >  		}
> > -	} else if (mem_is_bin(pIn3) || mem_is_bin(pIn1)) {
> > +	} else if (mem_is_bin_ext(pIn3) || mem_is_bin_ext(pIn1)) {
> >  		if (mem_cmp_bin(pIn3, pIn1, &res) != 0) {
> 
> 6. Arrays and maps are not comparable by memcmp(). The same numbers
> can be encoded in MessagePack as 8 bytes and as 1 byte (you can do that
> legally, just not widely used). In 2 maps elements might be in different
> order. And obviously you can't compare map and array. Such places would
> draw more attention for future fixes if we wouldn't mask them behind
> _ext() suffix but would rather use more explicit checks.
> 
I added an error on attempt to compare MAP or ARRAY to anything.

> > -			char *str = !mem_is_bin(pIn3) ?
> > +			char *str = !mem_is_bin_ext(pIn3) ?
> >  				    mem_type_to_str(pIn3) :
> >  				    mem_type_to_str(pIn1);
> >  			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
> > @@ -2991,7 +2991,7 @@ case OP_Found: {        /* jump, in3 */
> >  	} else {
> >  		pFree = pIdxKey = sqlVdbeAllocUnpackedRecord(db, pC->key_def);
> >  		if (pIdxKey==0) goto no_mem;
> > -		assert(mem_is_bin(pIn3));
> > +		assert(mem_is_bin_ext(pIn3));
> 
> 7. This would be mem_is_array() AFAIU. Because it is a tuple, right?
> 
It is true, that MEM contains MP_ARRAY. However, currently this MEM has
VARBINARY type. I tried to change type to ARRAY, but found unexpected for me
error: ARRAY cannot be inserted in SCALAR field, but VARBINARY can. Also,
we cannot create an index that contains MAP or ARRAY.It means that after
changing type to ARRAY, value created by OP_MakeRecord cannot be inserted to
most of ephemeral spaces, since all their fields have VARBINARY type and
their PK consist of all fields. I decided to drop my attempt and accept that
currently all internally created arrays are of VARBINARY type. Also, this
means that arrays and maps have undefined behaviour in our code, since
their behaviour depends on the source of the value.

So, for now most of maps and arrays we can just use mem_is_bin(), at least
for now.

I think that we have to rework out ephemeral spaces if we want to introduce
support of MAP and ARRAY in SQL.

> >  		(void)ExpandBlob(pIn3);
> >  		sqlVdbeRecordUnpackMsgpack(pC->key_def,
> >  					       pIn3->z, pIdxKey);
> > @@ -3253,7 +3253,7 @@ case OP_SorterData: {
> >  	assert(isSorter(pC));
> >  	if (sqlVdbeSorterRowkey(pC, pOut) != 0)
> >  		goto abort_due_to_error;
> > -	assert(mem_is_bin(pOut));
> > +	assert(mem_is_bin_ext(pOut));
> 
> 8. Ditto.
> 
Reverted.

> >  	assert(pOp->p1>=0 && pOp->p1<p->nCursor);
> >  	p->apCsr[pOp->p3]->cacheStatus = CACHE_STALE;
> >  	break;
> > @@ -3616,7 +3616,7 @@ case OP_SorterInsert: {      /* in2 */
> >  	assert(cursor != NULL);
> >  	assert(isSorter(cursor));
> >  	pIn2 = &aMem[pOp->p2];
> > -	assert(mem_is_bin(pIn2));
> > +	assert(mem_is_bin_ext(pIn2));
> 
> 9. Ditto.
> 
Reverted.

> >  	if (ExpandBlob(pIn2) != 0 ||
> >  	    sqlVdbeSorterWrite(cursor, pIn2) != 0)
> >  		goto abort_due_to_error;
> > @@ -3650,7 +3650,7 @@ case OP_SorterInsert: {      /* in2 */
> >  case OP_IdxReplace:
> >  case OP_IdxInsert: {
> >  	pIn2 = &aMem[pOp->p1];
> > -	assert(mem_is_bin(pIn2));
> > +	assert(mem_is_bin_ext(pIn2));
> 
> 10. Ditto.
> 
Reverted.

> >  	if (ExpandBlob(pIn2) != 0)
> >  		goto abort_due_to_error;
> >  	struct space *space;
> > @@ -3741,10 +3741,10 @@ case OP_Update: {
> >  	assert(pOp->p4type == P4_SPACEPTR);
> >  
> >  	struct Mem *key_mem = &aMem[pOp->p2];
> > -	assert(mem_is_bin(key_mem));
> > +	assert(mem_is_bin_ext(key_mem));
> 
> 11. Ditto here and below.
> 
Reverted.

> >  	struct Mem *upd_fields_mem = &aMem[pOp->p3];
> > -	assert(mem_is_bin(upd_fields_mem));
> > +	assert(mem_is_bin_ext(upd_fields_mem));
> >  	uint32_t *upd_fields = (uint32_t *)upd_fields_mem->z;
> >  	uint32_t upd_fields_cnt = upd_fields_mem->n / sizeof(uint32_t);


Diff:

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index ae5ead67e..90e8e152f 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -553,7 +553,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 	if (mem_is_null(argv[0]))
 		return;
-	if (mem_is_bin_ext(argv[0])) {
+	if (!mem_is_num(argv[0]) && !mem_is_str(argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
 			 mem_str(argv[0]), "numeric");
 		context->is_aborted = true;
@@ -613,7 +613,8 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
 	const char *z2;                                                        \
 	int n;                                                                 \
 	UNUSED_PARAMETER(argc);                                                \
-	if (mem_is_bin_ext(argv[0])) {                                         \
+	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||                      \
+	    mem_is_array(argv[0])) {                                           \
 		diag_set(ClientError, ER_INCONSISTENT_TYPES, "text",           \
 			 "varbinary");                                         \
 		context->is_aborted = true;                                    \
@@ -694,7 +695,8 @@ randomBlob(sql_context * context, int argc, sql_value ** argv)
 	unsigned char *p;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	if (mem_is_bin_ext(argv[0])) {
+	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||
+	    mem_is_array(argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
 			 mem_str(argv[0]), "numeric");
 		context->is_aborted = true;
@@ -1455,7 +1457,7 @@ trim_func_one_arg(struct sql_context *context, sql_value *arg)
 	const unsigned char *default_trim;
 	if (mem_is_null(arg))
 		return;
-	if (mem_is_bin_ext(arg))
+	if (mem_is_bin(arg))
 		default_trim = (const unsigned char *) "\0";
 	else
 		default_trim = (const unsigned char *) " ";
@@ -1584,7 +1586,8 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 		1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
 	};
 	assert(argc == 1);
-	if (mem_is_bin_ext(argv[0])) {
+	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||
+	    mem_is_array(argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
 			 mem_str(argv[0]), "text");
 		context->is_aborted = true;
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 6fc15617d..526b6bf3e 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -206,12 +206,6 @@ mem_is_array(const struct Mem *mem)
 	       mp_typeof(*mem->z) == MP_ARRAY;
 }
 
-static inline bool
-mem_is_bin_ext(const struct Mem *mem)
-{
-	return (mem->flags & MEM_Blob) != 0;
-}
-
 static inline bool
 mem_is_agg(const struct Mem *mem)
 {
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index bedfa87af..12ec703a2 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1634,15 +1634,20 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
 				 "boolean");
 			goto abort_due_to_error;
 		}
-	} else if (mem_is_bin_ext(pIn3) || mem_is_bin_ext(pIn1)) {
+	} else if (mem_is_bin(pIn3) || mem_is_bin(pIn1)) {
 		if (mem_cmp_bin(pIn3, pIn1, &res) != 0) {
-			char *str = !mem_is_bin_ext(pIn3) ?
+			char *str = !mem_is_bin(pIn3) ?
 				    mem_type_to_str(pIn3) :
 				    mem_type_to_str(pIn1);
 			diag_set(ClientError, ER_SQL_TYPE_MISMATCH, str,
 				 "varbinary");
 			goto abort_due_to_error;
 		}
+	} else if (mem_is_map(pIn3) || mem_is_map(pIn1) || mem_is_array(pIn3) ||
+		   mem_is_array(pIn1)) {
+		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+			 mem_type_to_str(pIn3), mem_type_to_str(pIn1));
+		goto abort_due_to_error;
 	} else if (type == FIELD_TYPE_STRING) {
 		if (mem_cmp_str(pIn3, pIn1, &res, pOp->p4.pColl) != 0) {
 			const char *str =
@@ -2991,7 +2996,7 @@ case OP_Found: {        /* jump, in3 */
 	} else {
 		pFree = pIdxKey = sqlVdbeAllocUnpackedRecord(db, pC->key_def);
 		if (pIdxKey==0) goto no_mem;
-		assert(mem_is_bin_ext(pIn3));
+		assert(mem_is_bin(pIn3));
 		(void)ExpandBlob(pIn3);
 		sqlVdbeRecordUnpackMsgpack(pC->key_def,
 					       pIn3->z, pIdxKey);
@@ -3253,7 +3258,7 @@ case OP_SorterData: {
 	assert(isSorter(pC));
 	if (sqlVdbeSorterRowkey(pC, pOut) != 0)
 		goto abort_due_to_error;
-	assert(mem_is_bin_ext(pOut));
+	assert(mem_is_bin(pOut));
 	assert(pOp->p1>=0 && pOp->p1<p->nCursor);
 	p->apCsr[pOp->p3]->cacheStatus = CACHE_STALE;
 	break;
@@ -3616,7 +3621,7 @@ case OP_SorterInsert: {      /* in2 */
 	assert(cursor != NULL);
 	assert(isSorter(cursor));
 	pIn2 = &aMem[pOp->p2];
-	assert(mem_is_bin_ext(pIn2));
+	assert(mem_is_bin(pIn2));
 	if (ExpandBlob(pIn2) != 0 ||
 	    sqlVdbeSorterWrite(cursor, pIn2) != 0)
 		goto abort_due_to_error;
@@ -3650,7 +3655,7 @@ case OP_SorterInsert: {      /* in2 */
 case OP_IdxReplace:
 case OP_IdxInsert: {
 	pIn2 = &aMem[pOp->p1];
-	assert(mem_is_bin_ext(pIn2));
+	assert(mem_is_bin(pIn2));
 	if (ExpandBlob(pIn2) != 0)
 		goto abort_due_to_error;
 	struct space *space;
@@ -3741,10 +3746,10 @@ case OP_Update: {
 	assert(pOp->p4type == P4_SPACEPTR);
 
 	struct Mem *key_mem = &aMem[pOp->p2];
-	assert(mem_is_bin_ext(key_mem));
+	assert(mem_is_bin(key_mem));
 
 	struct Mem *upd_fields_mem = &aMem[pOp->p3];
-	assert(mem_is_bin_ext(upd_fields_mem));
+	assert(mem_is_bin(upd_fields_mem));
 	uint32_t *upd_fields = (uint32_t *)upd_fields_mem->z;
 	uint32_t upd_fields_cnt = upd_fields_mem->n / sizeof(uint32_t);
 


Patch:

commit f6ad3ac1c548a2ffbfbe4b103b533f48db6458fa
Author: Mergen Imeev <imeevma@gmail.com>
Date:   Tue Apr 27 15:12:33 2021 +0300

    sql: make mem_is_bin() to check only for VARBINARY
    
    After this patch, the mem_is_bin() function will return 'true' only if
    the value that the MEM contains is of type VARBINARY. This patch also
    adds the mem_is_bin_ext() function, which is used to check if a MEM
    contains value of type VARBINARY or value of types that are currently
    considered VARBINARY extensions - MAP and ARRAY.
    
    Part of #4906

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 94302cc56..90e8e152f 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -553,7 +553,7 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 	if (mem_is_null(argv[0]))
 		return;
-	if (mem_is_bin(argv[0])) {
+	if (!mem_is_num(argv[0]) && !mem_is_str(argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
 			 mem_str(argv[0]), "numeric");
 		context->is_aborted = true;
@@ -613,7 +613,8 @@ case_type##ICUFunc(sql_context *context, int argc, sql_value **argv)   \
 	const char *z2;                                                        \
 	int n;                                                                 \
 	UNUSED_PARAMETER(argc);                                                \
-	if (mem_is_bin(argv[0])) {                                             \
+	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||                      \
+	    mem_is_array(argv[0])) {                                           \
 		diag_set(ClientError, ER_INCONSISTENT_TYPES, "text",           \
 			 "varbinary");                                         \
 		context->is_aborted = true;                                    \
@@ -694,7 +695,8 @@ randomBlob(sql_context * context, int argc, sql_value ** argv)
 	unsigned char *p;
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
-	if (mem_is_bin(argv[0])) {
+	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||
+	    mem_is_array(argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
 			 mem_str(argv[0]), "numeric");
 		context->is_aborted = true;
@@ -1584,7 +1586,8 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
 		1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
 	};
 	assert(argc == 1);
-	if (mem_is_bin(argv[0])) {
+	if (mem_is_bin(argv[0]) || mem_is_map(argv[0]) ||
+	    mem_is_array(argv[0])) {
 		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
 			 mem_str(argv[0]), "text");
 		context->is_aborted = true;
diff --git a/src/box/sql/mem.h b/src/box/sql/mem.h
index 1db7f4deb..526b6bf3e 100644
--- a/src/box/sql/mem.h
+++ b/src/box/sql/mem.h
@@ -184,7 +184,7 @@ mem_is_bool(const struct Mem *mem)
 static inline bool
 mem_is_bin(const struct Mem *mem)
 {
-	return (mem->flags & MEM_Blob) != 0;
+	return (mem->flags & MEM_Blob) != 0 && (mem->flags & MEM_Subtype) == 0;
 }
 
 static inline bool
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 2308587e7..12ec703a2 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1643,6 +1643,11 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
 				 "varbinary");
 			goto abort_due_to_error;
 		}
+	} else if (mem_is_map(pIn3) || mem_is_map(pIn1) || mem_is_array(pIn3) ||
+		   mem_is_array(pIn1)) {
+		diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
+			 mem_type_to_str(pIn3), mem_type_to_str(pIn1));
+		goto abort_due_to_error;
 	} else if (type == FIELD_TYPE_STRING) {
 		if (mem_cmp_str(pIn3, pIn1, &res, pOp->p4.pColl) != 0) {
 			const char *str =

      reply	other threads:[~2021-05-17 12:05 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <cover.1619542456.git.imeevma@gmail.com>
2021-04-27 16:55 ` [Tarantool-patches] [PATCH v2 1/3] sql: initialize MEM used in aggregate functions Mergen Imeev via Tarantool-patches
2021-04-29 21:02   ` Vladislav Shpilevoy via Tarantool-patches
2021-05-17 11:38     ` Mergen Imeev via Tarantool-patches
2021-04-27 16:55 ` [Tarantool-patches] [PATCH v2 2/3] sql: make mem_is_bin() to check only for VARBINARY Mergen Imeev via Tarantool-patches
2021-04-29 21:05   ` Vladislav Shpilevoy via Tarantool-patches
2021-05-17 12:05     ` Mergen Imeev via Tarantool-patches [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210517120522.GB177059@tarantool.org \
    --to=tarantool-patches@dev.tarantool.org \
    --cc=imeevma@tarantool.org \
    --cc=v.shpilevoy@tarantool.org \
    --subject='Re: [Tarantool-patches] [PATCH v2 2/3] sql: make mem_is_bin() to check only for VARBINARY' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

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