Tarantool development patches archive
 help / color / mirror / Atom feed
From: "n.pettik" <korablev@tarantool.org>
To: tarantool-patches@freelists.org
Cc: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Subject: [tarantool-patches] Re: [PATCH 5/8] sql: replace field type with affinity for VDBE runtime
Date: Wed, 16 Jan 2019 17:26:15 +0300	[thread overview]
Message-ID: <DD9F7BFD-719D-464A-910E-2E5E01AD0F00@tarantool.org> (raw)
In-Reply-To: <6e7623ee-fdcc-616f-6a99-20aff0e97f43@tarantool.org>

Alongside with fixes which you requested, I found that columns
in form of new.a or old.a (within trigger) have wrong calculated type.
Fix is simple:

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index c0957de70..d3a8644ce 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -65,6 +65,7 @@ sql_expr_type(struct Expr *pExpr)
                return pExpr->type;
        case TK_AGG_COLUMN:
        case TK_COLUMN:
+       case TK_TRIGGER:
                assert(pExpr->iColumn >= 0);
                return pExpr->space_def->fields[pExpr->iColumn].type;
        case TK_SELECT_COLUMN:


> Thanks for the patch! See 8 comments below.
> 
>> sql: replace field type with affinity for VDBE runtime
> 
> 1. Maybe vice versa? Replace affinity with field type?

You are right, fixed:

sql: replace affinity with field type for VDBE runtime

>> diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
>> index 51c63fa7a..a04dc8681 100644
>> --- a/src/box/sql/analyze.c
>> +++ b/src/box/sql/analyze.c
>> @@ -993,9 +993,10 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
>>  		sqlite3VdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
>>  		/* Add the entry to the stat1 table. */
>>  		callStatGet(v, stat4_reg, STAT_GET_STAT1, stat1_reg);
>> -		assert("BBB"[0] == AFFINITY_TEXT);
>> +		char types[3] = { FIELD_TYPE_STRING, FIELD_TYPE_STRING,
>> +				  FIELD_TYPE_STRING };
> 
> 2. How about explicit type? Not char[], but enum field_type[] ? It will
> look much more readable and convenient, IMO. Here and in other places.
> 
> 'type_str', used now in all places instead of affinity str, looks crutchy.

Ok, I don’t mind, but then we need to add explicit cast to char*:

diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index a04dc8681..aa6b28e3b 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -993,10 +993,12 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
                sqlite3VdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
                /* Add the entry to the stat1 table. */
                callStatGet(v, stat4_reg, STAT_GET_STAT1, stat1_reg);
-               char types[3] = { FIELD_TYPE_STRING, FIELD_TYPE_STRING,
-                                 FIELD_TYPE_STRING };
-               sqlite3VdbeAddOp4(v, OP_MakeRecord, tab_name_reg, 3, tmp_reg,
-                                 types, 3);
+               enum field_type types[4] = { FIELD_TYPE_STRING,
+                                            FIELD_TYPE_STRING,
+                                            FIELD_TYPE_STRING,
+                                            field_type_MAX };
+               sqlite3VdbeAddOp4(v, OP_MakeRecord, tab_name_reg, 4, tmp_reg,
+                                 (char *)types, sizeof(types));
                sqlite3VdbeAddOp4(v, OP_IdxInsert, tmp_reg, 0, 0,
                                  (char *)stat1, P4_SPACEPTR);

Other usages:

diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 7b0d6b2fd..ca6c49373 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -332,12 +332,12 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
                         */
                        key_len = 0;
                        struct index *pk = space_index(space, 0);
-                       char *type_str = is_view ? NULL :
-                                        sql_index_type_str(parse->db,
-                                                           pk->def);
+                       enum field_type *types = is_view ? NULL :
+                                                sql_index_type_str(parse->db,
+                                                                   pk->def);
                        sqlite3VdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len,
-                                         reg_key, type_str, is_view ? 0 :
-                                                            P4_DYNAMIC);
+                                         reg_key, (char *)types, is_view ? 0 :
+                                                                 P4_DYNAMIC);

diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index a6a8a24dd..8aa8ea01e 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -256,8 +256,8 @@ fkey_lookup_parent(struct Parse *parse_context, struct space *parent,
        struct index *idx = space_index(parent, referenced_idx);
        assert(idx != NULL);
        sqlite3VdbeAddOp4(v, OP_MakeRecord, temp_regs, field_count, rec_reg,
-                         sql_index_type_str(parse_context->db, idx->def),
-                         P4_DYNAMIC);
+                         (char *) sql_index_type_str(parse_context->db,
+                                                     idx->def), P4_DYNAMIC);

@@ -87,7 +88,9 @@ sql_emit_table_types(struct Vdbe *v, struct space_def *def, int reg)
        assert(reg > 0);
        struct sqlite3 *db = sqlite3VdbeDb(v);
        uint32_t field_count = def->field_count;
-       char *colls_type = (char *) sqlite3DbMallocZero(db, field_count + 1);
+       size_t sz = (field_count + 1) * sizeof(enum field_type);
+       enum field_type *colls_type =
+               (enum field_type *) sqlite3DbMallocZero(db, sz);
        if (colls_type == NULL)
                return;
        for (uint32_t i = 0; i < field_count; ++i) {
@@ -104,8 +107,9 @@ sql_emit_table_types(struct Vdbe *v, struct space_def *def, int reg)
                                          FIELD_TYPE_INTEGER);
                }
        }
-       sqlite3VdbeAddOp4(v, OP_ApplyType, reg, field_count, 0, colls_type,
-                         P4_DYNAMIC);
+       colls_type[field_count] = field_type_MAX;
+       sqlite3VdbeAddOp4(v, OP_ApplyType, reg, field_count, 0,
+                         (char *)colls_type, P4_DYNAMIC);

diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 0ee40093f..1cd4662cf 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1202,11 +1202,11 @@ selectInnerLoop(Parse * pParse,         /* The parser context */
                                int r1 = sqlite3GetTempReg(pParse);
                                assert(sqlite3Strlen30(pDest->zAffSdst) ==
                                       (unsigned int)nResultCol);
-                               char *type_str =
+                               enum field_type *types =
                                        sql_affinity_str_to_field_type_str(
                                                pDest->zAffSdst);
                                sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult,
-                                                 nResultCol, r1, type_str,
+                                                 nResultCol, r1, (char *)types,

@@ -1630,10 +1630,11 @@ generateSortTail(Parse * pParse,        /* Parsing context */
                        assert((unsigned int)nColumn ==
                               sqlite3Strlen30(pDest->zAffSdst));
 
-                       const char *type_str =
+                       enum field_type *types =
                                sql_affinity_str_to_field_type_str(pDest->zAffSdst);
                        sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, nColumn,
-                                         regTupleid, type_str, P4_DYNAMIC);
+                                         regTupleid, (char *)types,
+                                         P4_DYNAMIC);

@@ -3068,10 +3069,11 @@ generateOutputSubroutine(struct Parse *parse, struct Select *p,
                        int r1;
                        testcase(in->nSdst > 1);
                        r1 = sqlite3GetTempReg(parse);
-                       const char *type_str =
+                       enum field_type *types =
                                sql_affinity_str_to_field_type_str(dest->zAffSdst);
                        sqlite3VdbeAddOp4(v, OP_MakeRecord, in->iSdst,
-                                         in->nSdst, r1, type_str, P4_DYNAMIC);
+                                         in->nSdst, r1, (char *)types,
+                                         P4_DYNAMIC);

diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 41eae1550..fd74817ea 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -274,10 +274,12 @@ sqlite3Update(Parse * pParse,             /* The parser context */
                nKey = pk_part_count;
                regKey = iPk;
        } else {
-               char *type_str = is_view ? NULL :
-                                sql_index_type_str(pParse->db, pPk->def);
+               enum field_type *types = is_view ? NULL :
+                                        sql_index_type_str(pParse->db,
+                                                           pPk->def);
                sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, pk_part_count,
-                                 regKey, type_str, is_view ? 0 : P4_DYNAMIC);
+                                 regKey, (char *) types,
+                                 is_view ? 0 : P4_DYNAMIC);



>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index e51e2db2a..b3f98c317 100644
>> --- a/src/box/sql/build.c
>> +++ b/src/box/sql/build.c
>> @@ -520,6 +520,19 @@ sql_field_type_to_affinity(enum field_type field_type)
>>  	}
>>  }
>>  +char *
> 
> 2. So not char *, but enum field_type *. Ok?

Yep, see below.

> 
>> +sql_affinity_str_to_field_type_str(const char *affinity_str)
>> +{
>> +	if (affinity_str == NULL)
>> +		return NULL;
>> +	size_t len = strlen(affinity_str) + 1;
>> +	char *type_str = (char *) sqlite3DbMallocRaw(sql_get(), len);
>> +	for (uint32_t i = 0; i < len - 1; ++i)
>> +		type_str[i] = sql_affinity_to_field_type(affinity_str[i]);
>> +	type_str[len - 1] = '\0';
> 
> 2. Instead of 0 you can use field_type_MAX terminator, if we will move
> to enum field_type[].

Ok:

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index b3f98c317..70abe5e57 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -520,17 +520,18 @@ sql_field_type_to_affinity(enum field_type field_type)
        }
 }
 
-char *
+enum field_type *
 sql_affinity_str_to_field_type_str(const char *affinity_str)
 {
        if (affinity_str == NULL)
                return NULL;
-       size_t len = strlen(affinity_str) + 1;
-       char *type_str = (char *) sqlite3DbMallocRaw(sql_get(), len);
+       size_t len = (strlen(affinity_str) + 1)  * sizeof(enum field_type);
+       enum field_type *types =
+               (enum field_type *) sqlite3DbMallocRaw(sql_get(), len);
        for (uint32_t i = 0; i < len - 1; ++i)
-               type_str[i] = sql_affinity_to_field_type(affinity_str[i]);
-       type_str[len - 1] = '\0';
-       return type_str;
+               types[i] = sql_affinity_to_field_type(affinity_str[i]);
+       types[len - 1] = field_type_MAX;
+       return types;
 }
 
 /*

diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index fd36e2786..b122b872e 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -68,16 +68,17 @@ sql_space_index_affinity_str(struct sqlite3 *db, struct space_def *space_def,
        return aff;
 }
 
-char *
+enum field_type *
 sql_index_type_str(struct sqlite3 *db, const struct index_def *idx_def)
 {
        uint32_t column_count = idx_def->key_def->part_count;
-       char *types = (char *) sqlite3DbMallocRaw(db, column_count + 1);
+       uint32_t sz = (column_count + 1) * sizeof(enum field_type);
+       enum field_type *types = (enum field_type *) sqlite3DbMallocRaw(db, sz);
        if (types == NULL)
                return NULL;
        for (uint32_t i = 0; i < column_count; i++)
                types[i] = idx_def->key_def->parts[i].type;
-       types[column_count] = '\0';
+       types[column_count] = field_type_MAX;
        return types;
 }

diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 690fa6431..c8adc9ffe 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -3439,7 +3439,7 @@ sql_affinity_to_field_type(enum affinity_type affinity);
 enum affinity_type
 sql_field_type_to_affinity(enum field_type field_type);
 
-char *
+enum field_type *
 sql_affinity_str_to_field_type_str(const char *affinity_str);

@@ -4239,7 +4239,7 @@ sql_space_index_affinity_str(struct sqlite3 *db, struct space_def *space_def,
                             struct index_def *idx_def);
 
 /** Return string consisting of fields types of given index. */
-char *
+enum field_type *
 sql_index_type_str(struct sqlite3 *db, const struct index_def *idx_def);

diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 369fb4b79..61d73b676 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c

@@ -2813,12 +2813,12 @@ case OP_Column: {
  * memory cell in the range.
  */
 case OP_ApplyType: {
-       const char *type_str = pOp->p4.z;
+       enum field_type *type_str = (enum field_type *)pOp->p4.z;
        assert(type_str != NULL);
-       assert(type_str[pOp->p2] == '\0');
+       assert(type_str[pOp->p2] == field_type_MAX);
        pIn1 = &aMem[pOp->p1];
-       char type;
-       while((type = *(type_str++)) != '\0') {
+       enum field_type type;
+       while((type = *(type_str++)) != field_type_MAX) {
                assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
                assert(memIsValid(pIn1));
                if (mem_apply_type(pIn1, type) != 0) {
@@ -2855,7 +2855,6 @@ case OP_MakeRecord: {
        Mem *pData0;           /* First field to be combined into the record */
        Mem MAYBE_UNUSED *pLast;  /* Last field of the record */
        int nField;            /* Number of fields in the record */
-       char *zAffinity;       /* The affinity string for the record */
        u8 bIsEphemeral;
 
        /* Assuming the record contains N fields, the record format looks
@@ -2874,7 +2873,7 @@ case OP_MakeRecord: {
         * of the record to data0.
         */
        nField = pOp->p1;
-       zAffinity = pOp->p4.z;
+       enum field_type *types = (enum field_type *)pOp->p4.z;
        bIsEphemeral = pOp->p5;
        assert(nField>0 && pOp->p2>0 && pOp->p2+nField<=(p->nMem+1 - p->nCursor)+1);
        pData0 = &aMem[nField];
@@ -2889,11 +2888,11 @@ case OP_MakeRecord: {
        /* Apply the requested affinity to all inputs
         */
        assert(pData0<=pLast);
-       if (zAffinity) {
+       if (types != NULL) {
                pRec = pData0;
-               do{
-                       mem_apply_type(pRec++, *(zAffinity++));
-               }while( zAffinity[0]);
+               do {
+                       mem_apply_type(pRec++, *(types++));
+               } while(types[0] != field_type_MAX);
        }

diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index aae5d6617..efbc91cf8 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -397,8 +397,11 @@ codeApplyAffinity(Parse * pParse, int base, int n, char *zAff)
        }
 
        if (n > 0) {
-               const char *type_str = sql_affinity_str_to_field_type_str(zAff);
-               sqlite3VdbeAddOp4(v, OP_ApplyType, base, n, 0, type_str, n);
+               enum field_type *types =
+                       sql_affinity_str_to_field_type_str(zAff);
+               types[n] = field_type_MAX;
+               sqlite3VdbeAddOp4(v, OP_ApplyType, base, n, 0, (char *)types,
+                                 P4_DYNAMIC);


> 
>> +	return type_str;
>> +}
>> +
>>  /*
>>   * Add a new column to the table currently being constructed.
>>   *
>> diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
>> index f9c42fdec..7b0d6b2fd 100644
>> --- a/src/box/sql/delete.c
>> +++ b/src/box/sql/delete.c
>> @@ -332,12 +332,12 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
>>  			 */
>>  			key_len = 0;
>>  			struct index *pk = space_index(space, 0);
>> -			const char *zAff = is_view ? NULL :
>> -					   sql_space_index_affinity_str(parse->db,
>> -									space->def,
>> -									pk->def);
>> +			char *type_str = is_view ? NULL :
>> +					 sql_index_type_str(parse->db,
>> +							    pk->def);
>>  			sqlite3VdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len,
>> -					  reg_key, zAff, pk_len);
>> +					  reg_key, type_str, is_view ? 0 :
>> +							     P4_DYNAMIC);
> 
> 3. How did it work before your patch? Looks like it was a leak. Before the
> patch, pk_len was passed instead of STATIC/DYNAMIC.

Yep, type_str was copied inside vdbeChangeP4Full(), but zAff itself
would be never freed. Now it works as should.

> 
>>  			/* Set flag to save memory allocating one
>>  			 * by malloc.
>>  			 */
>> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
>> index 917e6e30b..c823c5a06 100644
>> --- a/src/box/sql/expr.c
>> +++ b/src/box/sql/expr.c
>> @@ -2858,8 +2858,11 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
>>  						jmpIfDynamic = -1;
>>  					}
>>  					r3 = sqlite3ExprCodeTarget(pParse, pE2, r1);
>> +					char type =
>> +						sql_affinity_to_field_type(affinity);
>>  	 				sqlite3VdbeAddOp4(v, OP_MakeRecord, r3,
>> -							  1, r2, &affinity, 1);
>> +							  1, r2, &type,
>> +							  1);
> 
> 4. I do not understand. Is p4type of sqlite3VdbeAddOp4 of OP_MakeRecord
> a length of an affinity string, or a type of its allocation? Or both?

Both. p4type > 0 means that p4type bytes are copied to freshly allocated memory
and P4 set to DYNAMIC. sqlite3DbStrNDup() in vdbeChangeP4Full() reserves
one more byte for NULL termination. After replacing char* with field_type:

+++ b/src/box/sql/expr.c
@@ -2858,11 +2858,12 @@ sqlite3CodeSubselect(Parse * pParse,    /* Parsing context */
                                                jmpIfDynamic = -1;
                                        }
                                        r3 = sqlite3ExprCodeTarget(pParse, pE2, r1);
-                                       char type =
+                                       enum field_type type =
                                                sql_affinity_to_field_type(affinity);
+                                       enum field_type types[2] = {type, field_type_MAX};
                                        sqlite3VdbeAddOp4(v, OP_MakeRecord, r3,
-                                                         1, r2, &type,
-                                                         1);
+                                                         1, r2, (char *)types,
+                                                         sizeof(types));

> 
> Also, I wonder how does this code works:
> 
> 	if (zAffinity) {
> 		pRec = pData0;
> 		do{
> 			mem_apply_type(pRec++, *(zAffinity++));
> 		}while( zAffinity[0]);
> 	}
> 
> in OP_MakeRecord. It assumes, that zAffinity is a null-terminated string,
> but in the code above you pass one char, without zero-termination.

It is OK, string is copied and null terminated, see explanation above.
Nevertheless, this code has been reworked, since now we use
field_type_MAX as a termination sign.

>>  					sqlite3ExprCacheAffinityChange(pParse,
>>  								       r3, 1);
>>  					sqlite3VdbeAddOp2(v, OP_IdxInsert, r2,
>> @@ -3172,7 +3175,8 @@ sqlite3ExprCodeIN(Parse * pParse,	/* Parsing and code generating context */
>>  	 * of the RHS using the LHS as a probe.  If found, the result is
>>  	 * true.
>>  	 */
>> -	sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector);
>> +	char *type_str = sql_affinity_str_to_field_type_str(zAff);
>> +	sqlite3VdbeAddOp4(v, OP_ApplyType, rLhs, nVector, 0, type_str, nVector);
> 
> 5. type_str is dynamically allocated, nVector (== p4type) is > 0. But freeP4
> function in vdbeaux.c does not know how to free p4type > 0. So it is definitely
> a leak. Please, validate all places where dynamic type string is created.

Yep, it is similar to the first one in this patch, but I’ve missed this place.
Should be P4_DYNAMIC. Moreover, now we use field_type_MAX to
indicate end of “string”, so we should cut it to follow original behaviour.
Fixed:

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index c823c5a06..2be083fb0 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -3175,8 +3177,10 @@ sqlite3ExprCodeIN(Parse * pParse,        /* Parsing and code generating context */
         * of the RHS using the LHS as a probe.  If found, the result is
         * true.
         */
-       char *type_str = sql_affinity_str_to_field_type_str(zAff);
-       sqlite3VdbeAddOp4(v, OP_ApplyType, rLhs, nVector, 0, type_str, nVector);
+       enum field_type *types = sql_affinity_str_to_field_type_str(zAff);
+       types[nVector] = field_type_MAX;
+       sqlite3VdbeAddOp4(v, OP_ApplyType, rLhs, nVector, 0, (char *)types,
+                         P4_DYNAMIC);

> 
>>  	if (destIfFalse == destIfNull) {
>>  		/* Combine Step 3 and Step 5 into a single opcode */
>>  		sqlite3VdbeAddOp4Int(v, OP_NotFound, pExpr->iTable,
>> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
>> index 4345af24e..369fb4b79 100644
>> --- a/src/box/sql/vdbe.c
>> +++ b/src/box/sql/vdbe.c
>> @@ -306,32 +306,35 @@ applyNumericAffinity(Mem *pRec, int bTryForInt)
>>  }
>>    /**
>> - * Processing is determine by the affinity parameter:
>> + * Processing is determine by the field type parameter:
>>   *
>> - * AFFINITY_INTEGER:
>> - * AFFINITY_REAL:
>> - *    Try to convert mem to an integer representation or a
>> - *    floating-point representation if an integer representation
>> - *    is not possible.  Note that the integer representation is
>> - *    always preferred, even if the affinity is REAL, because
>> - *    an integer representation is more space efficient on disk.
>> + * INTEGER:
>> + *    If memory holds floating point value and it can be
>> + *    converted without loss (2.0 - > 2), it's type is
>> + *    changed to INT. Otherwise, simply return success status.
>>   *
>> - * AFFINITY_TEXT:
>> - *    Convert mem to a text representation.
>> + * NUMBER:
>> + *    If memory holds INT or floating point value,
>> + *    no actions take place.
>>   *
>> - * AFFINITY_BLOB:
>> - *    No-op. mem is unchanged.
>> + * STRING:
>> + *    Convert mem to a string representation.
>>   *
>> - * @param record The value to apply affinity to.
>> - * @param affinity The affinity to be applied.
>> + * SCALAR:
>> + *    Mem is unchanged, but flat is set to BLOB.
> 
> 6. flat -> flag?

Fixed:

diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 369fb4b79..34365edd0 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -321,7 +321,7 @@ applyNumericAffinity(Mem *pRec, int bTryForInt)
  *    Convert mem to a string representation.
  *
  * SCALAR:
- *    Mem is unchanged, but flat is set to BLOB.
+ *    Mem is unchanged, but flag is set to BLOB.
  *

> 
>> + *
>> + * @param record The value to apply type to.
>> + * @param type_t The type to be applied.
>>   */
>>  static int
>> -mem_apply_affinity(struct Mem *record, enum affinity_type affinity)
>> +mem_apply_type(struct Mem *record, enum field_type f_type)
>>  {
>>  	if ((record->flags & MEM_Null) != 0)
>>  		return 0;
>> -	switch (affinity) {
>> -	case AFFINITY_INTEGER:
>> +	assert(f_type  < field_type_MAX);
> 
> 7. Double white-space.

Fixed:

@@ -331,7 +331,7 @@ mem_apply_type(struct Mem *record, enum field_type f_type)
 {
        if ((record->flags & MEM_Null) != 0)
                return 0;
-       assert(f_type  < field_type_MAX);
+       assert(f_type < field_type_MAX);

> 
>> +	switch (f_type) {
>> +	case FIELD_TYPE_INTEGER:
>> +	case FIELD_TYPE_UNSIGNED:
>>  		if ((record->flags & MEM_Int) == MEM_Int)
>>  			return 0;
>>  		if ((record->flags & MEM_Real) == MEM_Real) {
>> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
>> index 571b5af78..539296079 100644
>> --- a/src/box/sql/where.c
>> +++ b/src/box/sql/where.c
>> @@ -1200,7 +1200,7 @@ whereRangeSkipScanEst(Parse * pParse,		/* Parsing & code generating context */
>>  	int nLower = -1;
>>  	int nUpper = index->def->opts.stat->sample_count + 1;
>>  	int rc = SQLITE_OK;
>> -	u8 aff = sql_space_index_part_affinity(space->def, p, nEq);
>> +	u8 aff = p->key_def->parts[nEq].type;
> 
> 8. Why? Below in this function aff is used as affinity, not type.

Am I missing smth?
sqlite3Stat4ValueFromExpr -> stat4ValueFromExpr -> sqlite3ValueApplyAffinity -> mem_apply_type

And mem_apply_type operates on field_type, not affinity.

Also, renamed variable.

diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 539296079..6b8999dd9 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -1200,7 +1200,7 @@ whereRangeSkipScanEst(Parse * pParse,             /* Parsing & code generating context */
        int nLower = -1;
        int nUpper = index->def->opts.stat->sample_count + 1;
        int rc = SQLITE_OK;
-       u8 aff = p->key_def->parts[nEq].type;
+       enum field_type type = p->key_def->parts[nEq].type;
 
        sqlite3_value *p1 = 0;  /* Value extracted from pLower */
        sqlite3_value *p2 = 0;  /* Value extracted from pUpper */
@@ -1209,12 +1209,12 @@ whereRangeSkipScanEst(Parse * pParse,           /* Parsing & code generating context */
        struct coll *coll = p->key_def->parts[nEq].coll;
        if (pLower) {
                rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
-                                              aff, &p1);
+                                              type, &p1);
                nLower = 0;
        }
        if (pUpper && rc == SQLITE_OK) {
                rc = sqlite3Stat4ValueFromExpr(pParse, pUpper->pExpr->pRight,
-                                              aff, &p2);
+                                              type, &p2);
                nUpper = p2 ? 0 : index->def->opts.stat->sample_count;

Whole patch:

Author: Nikita Pettik <korablev@tarantool.org>
Date:   Fri Dec 21 13:23:07 2018 +0200

    sql: replace affinity with field type for VDBE runtime
    
    This stage of affinity removal requires introducing of auxiliary
    intermediate function to convert array of affinity values to field type
    values. The rest of job done in this commit is a straightforward
    refactoring.
    
    Part of #3698

diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index 51c63fa7a..982eaa974 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -993,9 +993,12 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
                sqlite3VdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
                /* Add the entry to the stat1 table. */
                callStatGet(v, stat4_reg, STAT_GET_STAT1, stat1_reg);
-               assert("BBB"[0] == AFFINITY_TEXT);
-               sqlite3VdbeAddOp4(v, OP_MakeRecord, tab_name_reg, 3, tmp_reg,
-                                 "BBB", 0);
+               enum field_type types[4] = { FIELD_TYPE_STRING,
+                                            FIELD_TYPE_STRING,
+                                            FIELD_TYPE_STRING,
+                                            field_type_MAX };
+               sqlite3VdbeAddOp4(v, OP_MakeRecord, tab_name_reg, 4, tmp_reg,
+                                 (char *)types, sizeof(types));
                sqlite3VdbeAddOp4(v, OP_IdxInsert, tmp_reg, 0, 0,
                                  (char *)stat1, P4_SPACEPTR);
                /* Add the entries to the stat4 table. */
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index e51e2db2a..514d0ca9d 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -520,6 +520,21 @@ sql_field_type_to_affinity(enum field_type field_type)
        }
 }
 
+enum field_type *
+sql_affinity_str_to_field_type_str(const char *affinity_str)
+{
+       if (affinity_str == NULL)
+               return NULL;
+       size_t len = strlen(affinity_str) + 1;
+       size_t sz = len * sizeof(enum field_type);
+       enum field_type *types =
+               (enum field_type *) sqlite3DbMallocRaw(sql_get(), sz);
+       for (uint32_t i = 0; i < len - 1; ++i)
+               types[i] = sql_affinity_to_field_type(affinity_str[i]);
+       types[len - 1] = field_type_MAX;
+       return types;
+}
+
 /*
  * Add a new column to the table currently being constructed.
  *
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index f9c42fdec..ca6c49373 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -332,12 +332,12 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
                         */
                        key_len = 0;
                        struct index *pk = space_index(space, 0);
-                       const char *zAff = is_view ? NULL :
-                                          sql_space_index_affinity_str(parse->db,
-                                                                       space->def,
-                                                                       pk->def);
+                       enum field_type *types = is_view ? NULL :
+                                                sql_index_type_str(parse->db,
+                                                                   pk->def);
                        sqlite3VdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len,
-                                         reg_key, zAff, pk_len);
+                                         reg_key, (char *)types, is_view ? 0 :
+                                                                 P4_DYNAMIC);
                        /* Set flag to save memory allocating one
                         * by malloc.
                         */
@@ -592,13 +592,13 @@ sql_generate_index_key(struct Parse *parse, struct index *index, int cursor,
                 * is an integer, then it might be stored in the
                 * table as an integer (using a compact
                 * representation) then converted to REAL by an
-                * OP_RealAffinity opcode. But we are getting
+                * OP_Realify opcode. But we are getting
                 * ready to store this value back into an index,
                 * where it should be converted by to INTEGER
-                * again.  So omit the OP_RealAffinity opcode if
+                * again.  So omit the OP_Realify opcode if
                 * it is present
                 */
-               sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
+               sqlite3VdbeDeletePriorOpcode(v, OP_Realify);
        }
        if (reg_out != 0)
                sqlite3VdbeAddOp3(v, OP_MakeRecord, reg_base, col_cnt, reg_out);
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 0a80ca622..ca39faf51 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2146,10 +2146,10 @@ sqlite3ExprCanBeNull(const Expr * p)
 
 /*
  * Return TRUE if the given expression is a constant which would be
- * unchanged by OP_Affinity with the affinity given in the second
+ * unchanged by OP_ApplyType with the type given in the second
  * argument.
  *
- * This routine is used to determine if the OP_Affinity operation
+ * This routine is used to determine if the OP_ApplyType operation
  * can be omitted.  When in doubt return FALSE.  A false negative
  * is harmless.  A false positive, however, can result in the wrong
  * answer.
@@ -2858,8 +2858,13 @@ sqlite3CodeSubselect(Parse * pParse,     /* Parsing context */
                                                jmpIfDynamic = -1;
                                        }
                                        r3 = sqlite3ExprCodeTarget(pParse, pE2, r1);
+                                       enum field_type type =
+                                               sql_affinity_to_field_type(affinity);
+                                       enum field_type types[2] =
+                                               { type, field_type_MAX };
                                        sqlite3VdbeAddOp4(v, OP_MakeRecord, r3,
-                                                         1, r2, &affinity, 1);
+                                                         1, r2, (char *)types,
+                                                         sizeof(types));
                                        sqlite3ExprCacheAffinityChange(pParse,
                                                                       r3, 1);
                                        sqlite3VdbeAddOp2(v, OP_IdxInsert, r2,
@@ -3172,7 +3177,10 @@ sqlite3ExprCodeIN(Parse * pParse,        /* Parsing and code generating context */
         * of the RHS using the LHS as a probe.  If found, the result is
         * true.
         */
-       sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector);
+       enum field_type *types = sql_affinity_str_to_field_type_str(zAff);
+       types[nVector] = field_type_MAX;
+       sqlite3VdbeAddOp4(v, OP_ApplyType, rLhs, nVector, 0, (char *)types,
+                         P4_DYNAMIC);
        if (destIfFalse == destIfNull) {
                /* Combine Step 3 and Step 5 into a single opcode */
                sqlite3VdbeAddOp4Int(v, OP_NotFound, pExpr->iTable,
@@ -3700,7 +3708,7 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
                                                  pCol->iSorterColumn, target);
                                if (pCol->space_def->fields[pExpr->iAgg].type ==
                                    FIELD_TYPE_NUMBER) {
-                                       sqlite3VdbeAddOp1(v, OP_RealAffinity,
+                                       sqlite3VdbeAddOp1(v, OP_Realify,
                                                          target);
                                }
                                return target;
@@ -3800,7 +3808,8 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
                                sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target);
                                inReg = target;
                        }
-                       sqlite3VdbeAddOp2(v, OP_Cast, target, pExpr->affinity);
+                       sqlite3VdbeAddOp2(v, OP_Cast, target,
+                                         sql_affinity_to_field_type(pExpr->affinity));
                        testcase(usedAsColumnCache(pParse, inReg, inReg));
                        sqlite3ExprCacheAffinityChange(pParse, inReg, 1);
                        return inReg;
@@ -4236,14 +4245,14 @@ sqlite3ExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 
 #ifndef SQLITE_OMIT_FLOATING_POINT
                        /* If the column has REAL affinity, it may currently be stored as an
-                        * integer. Use OP_RealAffinity to make sure it is really real.
+                        * integer. Use OP_Realify to make sure it is really real.
                         *
                         * EVIDENCE-OF: R-60985-57662 SQLite will convert the value back to
                         * floating point when extracting it from the record.
                         */
                        if (pExpr->iColumn >= 0 && def->fields[
                                pExpr->iColumn].affinity == AFFINITY_REAL) {
-                               sqlite3VdbeAddOp1(v, OP_RealAffinity, target);
+                               sqlite3VdbeAddOp1(v, OP_Realify, target);
                        }
 #endif
                        break;
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index 4e3270f0c..8aa8ea01e 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -256,9 +256,8 @@ fkey_lookup_parent(struct Parse *parse_context, struct space *parent,
        struct index *idx = space_index(parent, referenced_idx);
        assert(idx != NULL);
        sqlite3VdbeAddOp4(v, OP_MakeRecord, temp_regs, field_count, rec_reg,
-                         sql_space_index_affinity_str(parse_context->db,
-                                                      parent->def, idx->def),
-                         P4_DYNAMIC);
+                         (char *) sql_index_type_str(parse_context->db,
+                                                     idx->def), P4_DYNAMIC);
        sqlite3VdbeAddOp4Int(v, OP_Found, cursor, ok_label, rec_reg, 0);
        sqlite3ReleaseTempReg(parse_context, rec_reg);
        sqlite3ReleaseTempRange(parse_context, temp_regs, field_count);
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 6b76bb6da..b122b872e 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -68,17 +68,33 @@ sql_space_index_affinity_str(struct sqlite3 *db, struct space_def *space_def,
        return aff;
 }
 
+enum field_type *
+sql_index_type_str(struct sqlite3 *db, const struct index_def *idx_def)
+{
+       uint32_t column_count = idx_def->key_def->part_count;
+       uint32_t sz = (column_count + 1) * sizeof(enum field_type);
+       enum field_type *types = (enum field_type *) sqlite3DbMallocRaw(db, sz);
+       if (types == NULL)
+               return NULL;
+       for (uint32_t i = 0; i < column_count; i++)
+               types[i] = idx_def->key_def->parts[i].type;
+       types[column_count] = field_type_MAX;
+       return types;
+}
+
 void
-sql_emit_table_affinity(struct Vdbe *v, struct space_def *def, int reg)
+sql_emit_table_types(struct Vdbe *v, struct space_def *def, int reg)
 {
        assert(reg > 0);
        struct sqlite3 *db = sqlite3VdbeDb(v);
        uint32_t field_count = def->field_count;
-       char *colls_aff = (char *) sqlite3DbMallocZero(db, field_count + 1);
-       if (colls_aff == NULL)
+       size_t sz = (field_count + 1) * sizeof(enum field_type);
+       enum field_type *colls_type =
+               (enum field_type *) sqlite3DbMallocZero(db, sz);
+       if (colls_type == NULL)
                return;
        for (uint32_t i = 0; i < field_count; ++i) {
-               colls_aff[i] = def->fields[i].affinity;
+               colls_type[i] = def->fields[i].type;
                /*
                 * Force INTEGER type to handle queries like:
                 * CREATE TABLE t1 (id INT PRIMARY KEY);
@@ -86,13 +102,14 @@ sql_emit_table_affinity(struct Vdbe *v, struct space_def *def, int reg)
                 *
                 * In this case 1.123 should be truncated to 1.
                 */
-               if (colls_aff[i] == AFFINITY_INTEGER) {
+               if (colls_type[i] == FIELD_TYPE_INTEGER) {
                        sqlite3VdbeAddOp2(v, OP_Cast, reg + i,
-                                         AFFINITY_INTEGER);
+                                         FIELD_TYPE_INTEGER);
                }
        }
-       sqlite3VdbeAddOp4(v, OP_Affinity, reg, field_count, 0, colls_aff,
-                         P4_DYNAMIC);
+       colls_type[field_count] = field_type_MAX;
+       sqlite3VdbeAddOp4(v, OP_ApplyType, reg, field_count, 0,
+                         (char *)colls_type, P4_DYNAMIC);
 }
 
 /**
@@ -616,7 +633,7 @@ sqlite3Insert(Parse * pParse,       /* Parser context */
                 * table column affinities.
                 */
                if (!is_view)
-                       sql_emit_table_affinity(v, pTab->def, regCols + 1);
+                       sql_emit_table_types(v, pTab->def, regCols + 1);
 
                /* Fire BEFORE or INSTEAD OF triggers */
                vdbe_code_row_trigger(pParse, trigger, TK_INSERT, 0,
@@ -964,7 +981,7 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct Table *tab,
                        sqlite3VdbeResolveLabel(v, all_ok);
                }
        }
-       sql_emit_table_affinity(v, tab->def, new_tuple_reg);
+       sql_emit_table_types(v, tab->def, new_tuple_reg);
        /*
         * If PK is marked as INTEGER, use it as strict type,
         * not as affinity. Emit code for type checking.
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index fa0fea6c8..4b6983842 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1202,9 +1202,12 @@ selectInnerLoop(Parse * pParse,          /* The parser context */
                                int r1 = sqlite3GetTempReg(pParse);
                                assert(sqlite3Strlen30(pDest->zAffSdst) ==
                                       (unsigned int)nResultCol);
+                               enum field_type *types =
+                                       sql_affinity_str_to_field_type_str(
+                                               pDest->zAffSdst);
                                sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult,
-                                                 nResultCol, r1,
-                                                 pDest->zAffSdst, nResultCol);
+                                                 nResultCol, r1, (char *)types,
+                                                 P4_DYNAMIC);
                                sqlite3ExprCacheAffinityChange(pParse,
                                                               regResult,
                                                               nResultCol);
@@ -1626,8 +1629,12 @@ generateSortTail(Parse * pParse, /* Parsing context */
        case SRT_Set:{
                        assert((unsigned int)nColumn ==
                               sqlite3Strlen30(pDest->zAffSdst));
+
+                       enum field_type *types =
+                               sql_affinity_str_to_field_type_str(pDest->zAffSdst);
                        sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, nColumn,
-                                         regTupleid, pDest->zAffSdst, nColumn);
+                                         regTupleid, (char *)types,
+                                         P4_DYNAMIC);
                        sqlite3ExprCacheAffinityChange(pParse, regRow, nColumn);
                        sqlite3VdbeAddOp2(v, OP_IdxInsert, regTupleid, pDest->reg_eph);
                        break;
@@ -3062,9 +3069,11 @@ generateOutputSubroutine(struct Parse *parse, struct Select *p,
                        int r1;
                        testcase(in->nSdst > 1);
                        r1 = sqlite3GetTempReg(parse);
+                       enum field_type *types =
+                               sql_affinity_str_to_field_type_str(dest->zAffSdst);
                        sqlite3VdbeAddOp4(v, OP_MakeRecord, in->iSdst,
-                                         in->nSdst, r1, dest->zAffSdst,
-                                         in->nSdst);
+                                         in->nSdst, r1, (char *)types,
+                                         P4_DYNAMIC);
                        sqlite3ExprCacheAffinityChange(parse, in->iSdst,
                                                       in->nSdst);
                        sqlite3VdbeAddOp2(v, OP_IdxInsert, r1, dest->reg_eph);
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 2a7223fff..579f68fed 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -3439,6 +3439,9 @@ sql_affinity_to_field_type(enum affinity_type affinity);
 enum affinity_type
 sql_field_type_to_affinity(enum field_type field_type);
 
+enum field_type *
+sql_affinity_str_to_field_type_str(const char *affinity_str);
+
 /**
  * Compile view, i.e. create struct Select from
  * 'CREATE VIEW...' string, and assign cursors to each table from
@@ -4235,16 +4238,20 @@ char *
 sql_space_index_affinity_str(struct sqlite3 *db, struct space_def *space_def,
                             struct index_def *idx_def);
 
+/** Return string consisting of fields types of given index. */
+enum field_type *
+sql_index_type_str(struct sqlite3 *db, const struct index_def *idx_def);
+
 /**
- * Code an OP_Affinity opcode that will set affinities
+ * Code an OP_ApplyType opcode that will force types
  * for given range of register starting from @reg.
  *
  * @param v VDBE.
  * @param def Definition of table to be used.
- * @param reg Register where affinities will be placed.
+ * @param reg Register where types will be placed.
  */
 void
-sql_emit_table_affinity(struct Vdbe *v, struct space_def *def, int reg);
+sql_emit_table_types(struct Vdbe *v, struct space_def *def, int reg);
 
 /**
  * Return superposition of two affinities.
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 0e2d0fde8..fd74817ea 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -44,7 +44,7 @@ sqlite3ColumnDefault(Vdbe *v, struct space_def *def, int i, int ireg)
        assert(def != 0);
        if (!def->opts.is_view) {
                sqlite3_value *pValue = 0;
-               char affinity = def->fields[i].affinity;
+               enum field_type type = def->fields[i].type;
                VdbeComment((v, "%s.%s", def->name, def->fields[i].name));
                assert(i < (int)def->field_count);
 
@@ -52,14 +52,14 @@ sqlite3ColumnDefault(Vdbe *v, struct space_def *def, int i, int ireg)
                assert(def->fields != NULL && i < (int)def->field_count);
                if (def->fields != NULL)
                        expr = def->fields[i].default_value_expr;
-               sqlite3ValueFromExpr(sqlite3VdbeDb(v), expr, affinity,
+               sqlite3ValueFromExpr(sqlite3VdbeDb(v), expr, type,
                                     &pValue);
                if (pValue) {
                        sqlite3VdbeAppendP4(v, pValue, P4_MEM);
                }
 #ifndef SQLITE_OMIT_FLOATING_POINT
-               if (affinity == AFFINITY_REAL) {
-                       sqlite3VdbeAddOp1(v, OP_RealAffinity, ireg);
+               if (type == FIELD_TYPE_NUMBER) {
+                       sqlite3VdbeAddOp1(v, OP_Realify, ireg);
                }
 #endif
        }
@@ -274,11 +274,12 @@ sqlite3Update(Parse * pParse,             /* The parser context */
                nKey = pk_part_count;
                regKey = iPk;
        } else {
-               const char *zAff = is_view ? 0 :
-                                  sql_space_index_affinity_str(pParse->db, def,
-                                                               pPk->def);
+               enum field_type *types = is_view ? NULL :
+                                        sql_index_type_str(pParse->db,
+                                                           pPk->def);
                sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, pk_part_count,
-                                 regKey, zAff, pk_part_count);
+                                 regKey, (char *) types,
+                                 is_view ? 0 : P4_DYNAMIC);
                /*
                 * Set flag to save memory allocating one by
                 * malloc.
@@ -390,7 +391,7 @@ sqlite3Update(Parse * pParse,               /* The parser context */
         * verified. One could argue that this is wrong.
         */
        if (tmask & TRIGGER_BEFORE) {
-               sql_emit_table_affinity(v, pTab->def, regNew);
+               sql_emit_table_types(v, pTab->def, regNew);
                vdbe_code_row_trigger(pParse, trigger, TK_UPDATE, pChanges,
                                      TRIGGER_BEFORE, pTab, regOldPk,
                                      on_error, labelContinue);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 4345af24e..61d73b676 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -306,32 +306,35 @@ applyNumericAffinity(Mem *pRec, int bTryForInt)
 }
 
 /**
- * Processing is determine by the affinity parameter:
+ * Processing is determine by the field type parameter:
  *
- * AFFINITY_INTEGER:
- * AFFINITY_REAL:
- *    Try to convert mem to an integer representation or a
- *    floating-point representation if an integer representation
- *    is not possible.  Note that the integer representation is
- *    always preferred, even if the affinity is REAL, because
- *    an integer representation is more space efficient on disk.
+ * INTEGER:
+ *    If memory holds floating point value and it can be
+ *    converted without loss (2.0 - > 2), it's type is
+ *    changed to INT. Otherwise, simply return success status.
  *
- * AFFINITY_TEXT:
- *    Convert mem to a text representation.
+ * NUMBER:
+ *    If memory holds INT or floating point value,
+ *    no actions take place.
  *
- * AFFINITY_BLOB:
- *    No-op. mem is unchanged.
+ * STRING:
+ *    Convert mem to a string representation.
  *
- * @param record The value to apply affinity to.
- * @param affinity The affinity to be applied.
+ * SCALAR:
+ *    Mem is unchanged, but flag is set to BLOB.
+ *
+ * @param record The value to apply type to.
+ * @param type_t The type to be applied.
  */
 static int
-mem_apply_affinity(struct Mem *record, enum affinity_type affinity)
+mem_apply_type(struct Mem *record, enum field_type f_type)
 {
        if ((record->flags & MEM_Null) != 0)
                return 0;
-       switch (affinity) {
-       case AFFINITY_INTEGER:
+       assert(f_type < field_type_MAX);
+       switch (f_type) {
+       case FIELD_TYPE_INTEGER:
+       case FIELD_TYPE_UNSIGNED:
                if ((record->flags & MEM_Int) == MEM_Int)
                        return 0;
                if ((record->flags & MEM_Real) == MEM_Real) {
@@ -343,11 +346,11 @@ mem_apply_affinity(struct Mem *record, enum affinity_type affinity)
                        return 0;
                }
                return sqlite3VdbeMemIntegerify(record, false);
-       case AFFINITY_REAL:
+       case FIELD_TYPE_NUMBER:
                if ((record->flags & (MEM_Real | MEM_Int)) != 0)
                        return 0;
                return sqlite3VdbeMemRealify(record);
-       case AFFINITY_TEXT:
+       case FIELD_TYPE_STRING:
                /*
                 * Only attempt the conversion to TEXT if there is
                 * an integer or real representation (BLOB and
@@ -359,7 +362,7 @@ mem_apply_affinity(struct Mem *record, enum affinity_type affinity)
                }
                record->flags &= ~(MEM_Real | MEM_Int);
                return 0;
-       case AFFINITY_BLOB:
+       case FIELD_TYPE_SCALAR:
                if (record->flags & (MEM_Str | MEM_Blob))
                        record->flags |= MEM_Blob;
                return 0;
@@ -385,7 +388,7 @@ int sqlite3_value_numeric_type(sqlite3_value *pVal) {
 }
 
 /*
- * Exported version of mem_apply_affinity(). This one works on sqlite3_value*,
+ * Exported version of mem_apply_type(). This one works on sqlite3_value*,
  * not the internal Mem* type.
  */
 void
@@ -393,7 +396,7 @@ sqlite3ValueApplyAffinity(
        sqlite3_value *pVal,
        u8 affinity)
 {
-       mem_apply_affinity((Mem *) pVal, affinity);
+       mem_apply_type((Mem *) pVal, affinity);
 }
 
 /*
@@ -1946,7 +1949,7 @@ case OP_AddImm: {            /* in1 */
 case OP_MustBeInt: {            /* jump, in1 */
        pIn1 = &aMem[pOp->p1];
        if ((pIn1->flags & MEM_Int)==0) {
-               mem_apply_affinity(pIn1, AFFINITY_INTEGER);
+               mem_apply_type(pIn1, FIELD_TYPE_INTEGER);
                VdbeBranchTaken((pIn1->flags&MEM_Int)==0, 2);
                if ((pIn1->flags & MEM_Int)==0) {
                        if (pOp->p2==0) {
@@ -1962,16 +1965,16 @@ case OP_MustBeInt: {            /* jump, in1 */
 }
 
 #ifndef SQLITE_OMIT_FLOATING_POINT
-/* Opcode: RealAffinity P1 * * * *
+/* Opcode: Realify P1 * * * *
  *
  * If register P1 holds an integer convert it to a real value.
  *
  * This opcode is used when extracting information from a column that
- * has REAL affinity.  Such column values may still be stored as
+ * has float type.  Such column values may still be stored as
  * integers, for space efficiency, but after extraction we want them
  * to have only a real value.
  */
-case OP_RealAffinity: {                  /* in1 */
+case OP_Realify: {                  /* in1 */
        pIn1 = &aMem[pOp->p1];
        if (pIn1->flags & MEM_Int) {
                sqlite3VdbeMemRealify(pIn1);
@@ -1982,7 +1985,7 @@ case OP_RealAffinity: {                  /* in1 */
 
 #ifndef SQLITE_OMIT_CAST
 /* Opcode: Cast P1 P2 * * *
- * Synopsis: affinity(r[P1])
+ * Synopsis: type(r[P1])
  *
  * Force the value in register P1 to be the type defined by P2.
  *
@@ -1997,11 +2000,6 @@ case OP_RealAffinity: {                  /* in1 */
  * A NULL value is not changed by this routine.  It remains NULL.
  */
 case OP_Cast: {                  /* in1 */
-       assert(pOp->p2>=AFFINITY_BLOB && pOp->p2<=AFFINITY_REAL);
-       testcase( pOp->p2==AFFINITY_TEXT);
-       testcase( pOp->p2==AFFINITY_BLOB);
-       testcase( pOp->p2==AFFINITY_INTEGER);
-       testcase( pOp->p2==AFFINITY_REAL);
        pIn1 = &aMem[pOp->p1];
        rc = ExpandBlob(pIn1);
        if (rc != 0)
@@ -2011,7 +2009,7 @@ case OP_Cast: {                  /* in1 */
        if (rc == 0)
                break;
        diag_set(ClientError, ER_SQL_TYPE_MISMATCH, sqlite3_value_text(pIn1),
-                affinity_type_str(pOp->p2));
+                field_type_strs[pOp->p2]);
        rc = SQL_TARANTOOL_ERROR;
        goto abort_due_to_error;
 }
@@ -2225,7 +2223,7 @@ case OP_Ge: {             /* same as TK_GE, jump, in1, in3 */
        default:       res2 = res>=0;     break;
        }
 
-       /* Undo any changes made by mem_apply_affinity() to the input registers. */
+       /* Undo any changes made by mem_apply_type() to the input registers. */
        assert((pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn));
        pIn1->flags = flags1;
        assert((pIn3->flags & MEM_Dyn) == (flags3 & MEM_Dyn));
@@ -2814,21 +2812,19 @@ case OP_Column: {
  * string indicates the column affinity that should be used for the nth
  * memory cell in the range.
  */
-case OP_Affinity: {
-       const char *zAffinity;   /* The affinity to be applied */
-       char cAff;               /* A single character of affinity */
-
-       zAffinity = pOp->p4.z;
-       assert(zAffinity!=0);
-       assert(zAffinity[pOp->p2]==0);
+case OP_ApplyType: {
+       enum field_type *type_str = (enum field_type *)pOp->p4.z;
+       assert(type_str != NULL);
+       assert(type_str[pOp->p2] == field_type_MAX);
        pIn1 = &aMem[pOp->p1];
-       while( (cAff = *(zAffinity++))!=0) {
+       enum field_type type;
+       while((type = *(type_str++)) != field_type_MAX) {
                assert(pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)]);
                assert(memIsValid(pIn1));
-               if (mem_apply_affinity(pIn1, cAff) != 0) {
+               if (mem_apply_type(pIn1, type) != 0) {
                        diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
                                 sqlite3_value_text(pIn1),
-                                affinity_type_str(cAff));
+                                field_type_strs[type]);
                        rc = SQL_TARANTOOL_ERROR;
                        goto abort_due_to_error;
                }
@@ -2848,10 +2844,7 @@ case OP_Affinity: {
  * string indicates the column affinity that should be used for the nth
  * field of the index key.
  *
- * The mapping from character to affinity is given by the AFFINITY_
- * macros defined in sqliteInt.h.
- *
- * If P4 is NULL then all index fields have the affinity BLOB.
+ * If P4 is NULL then all index fields have type SCALAR.
  *
  * If P5 is not NULL then record under construction is intended to be inserted
  * into ephemeral space. Thus, sort of memory optimization can be performed.
@@ -2862,7 +2855,6 @@ case OP_MakeRecord: {
        Mem *pData0;           /* First field to be combined into the record */
        Mem MAYBE_UNUSED *pLast;  /* Last field of the record */
        int nField;            /* Number of fields in the record */
-       char *zAffinity;       /* The affinity string for the record */
        u8 bIsEphemeral;
 
        /* Assuming the record contains N fields, the record format looks
@@ -2881,7 +2873,7 @@ case OP_MakeRecord: {
         * of the record to data0.
         */
        nField = pOp->p1;
-       zAffinity = pOp->p4.z;
+       enum field_type *types = (enum field_type *)pOp->p4.z;
        bIsEphemeral = pOp->p5;
        assert(nField>0 && pOp->p2>0 && pOp->p2+nField<=(p->nMem+1 - p->nCursor)+1);
        pData0 = &aMem[nField];
@@ -2896,12 +2888,11 @@ case OP_MakeRecord: {
        /* Apply the requested affinity to all inputs
         */
        assert(pData0<=pLast);
-       if (zAffinity) {
+       if (types != NULL) {
                pRec = pData0;
-               do{
-                       mem_apply_affinity(pRec++, *(zAffinity++));
-                       assert(zAffinity[0]==0 || pRec<=pLast);
-               }while( zAffinity[0]);
+               do {
+                       mem_apply_type(pRec++, *(types++));
+               } while(types[0] != field_type_MAX);
        }
 
        /* Loop through the elements that will make up the record to figure
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 50bc35b2b..879ba34d0 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -483,7 +483,7 @@ int sqlite3VdbeRealValue(Mem *, double *);
 int sqlite3VdbeIntegerAffinity(Mem *);
 int sqlite3VdbeMemRealify(Mem *);
 int sqlite3VdbeMemNumerify(Mem *);
-int sqlite3VdbeMemCast(Mem *, u8);
+int sqlite3VdbeMemCast(Mem *, enum field_type type);
 int sqlite3VdbeMemFromBtree(BtCursor *, u32, u32, Mem *);
 void sqlite3VdbeMemRelease(Mem * p);
 int sqlite3VdbeMemFinalize(Mem *, FuncDef *);
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index bb4d91aed..cd71641b0 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -598,11 +598,12 @@ sqlite3VdbeMemNumerify(Mem * pMem)
  * used (for example) to implement the SQL "cast()" operator.
  */
 int
-sqlite3VdbeMemCast(Mem * pMem, u8 aff)
+sqlite3VdbeMemCast(Mem * pMem, enum field_type type)
 {
+       assert(type < field_type_MAX);
        if (pMem->flags & MEM_Null)
                return SQLITE_OK;
-       if ((pMem->flags & MEM_Blob) != 0 && aff == AFFINITY_REAL) {
+       if ((pMem->flags & MEM_Blob) != 0 && type == FIELD_TYPE_NUMBER) {
                if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, pMem->n) == 0) {
                        MemSetTypeFlag(pMem, MEM_Real);
                        pMem->u.r = pMem->u.i;
@@ -610,8 +611,8 @@ sqlite3VdbeMemCast(Mem * pMem, u8 aff)
                }
                return ! sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n);
        }
-       switch (aff) {
-       case AFFINITY_BLOB:
+       switch (type) {
+       case FIELD_TYPE_SCALAR:
                if (pMem->flags & MEM_Blob)
                        return SQLITE_OK;
                if (pMem->flags & MEM_Str) {
@@ -625,7 +626,7 @@ sqlite3VdbeMemCast(Mem * pMem, u8 aff)
                        return 0;
                }
                return SQLITE_ERROR;
-       case AFFINITY_INTEGER:
+       case FIELD_TYPE_INTEGER:
                if ((pMem->flags & MEM_Blob) != 0) {
                        if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i,
                                       pMem->n) != 0)
@@ -634,13 +635,13 @@ sqlite3VdbeMemCast(Mem * pMem, u8 aff)
                        return 0;
                }
                return sqlite3VdbeMemIntegerify(pMem, true);
-       case AFFINITY_REAL:
+       case FIELD_TYPE_NUMBER:
                return sqlite3VdbeMemRealify(pMem);
        default:
-               assert(aff == AFFINITY_TEXT);
+               assert(type == FIELD_TYPE_STRING);
                assert(MEM_Str == (MEM_Blob >> 3));
                pMem->flags |= (pMem->flags & MEM_Blob) >> 3;
-               sqlite3ValueApplyAffinity(pMem, AFFINITY_TEXT);
+               sqlite3ValueApplyAffinity(pMem, FIELD_TYPE_STRING);
                assert(pMem->flags & MEM_Str || pMem->db->mallocFailed);
                pMem->flags &= ~(MEM_Int | MEM_Real | MEM_Blob | MEM_Zero);
                return SQLITE_OK;
@@ -1579,6 +1580,7 @@ sqlite3Stat4ProbeSetValue(Parse * pParse, /* Parse context */
                        u8 aff = sql_space_index_part_affinity(space->def, idx,
                                                               iVal + i);
                        alloc.iVal = iVal + i;
+                       aff = sql_affinity_to_field_type(aff);
                        rc = stat4ValueFromExpr(pParse, pElem, aff, &alloc,
                                                &pVal);
                        if (!pVal)
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 571b5af78..6b8999dd9 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -1200,7 +1200,7 @@ whereRangeSkipScanEst(Parse * pParse,             /* Parsing & code generating context */
        int nLower = -1;
        int nUpper = index->def->opts.stat->sample_count + 1;
        int rc = SQLITE_OK;
-       u8 aff = sql_space_index_part_affinity(space->def, p, nEq);
+       enum field_type type = p->key_def->parts[nEq].type;
 
        sqlite3_value *p1 = 0;  /* Value extracted from pLower */
        sqlite3_value *p2 = 0;  /* Value extracted from pUpper */
@@ -1209,12 +1209,12 @@ whereRangeSkipScanEst(Parse * pParse,           /* Parsing & code generating context */
        struct coll *coll = p->key_def->parts[nEq].coll;
        if (pLower) {
                rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight,
-                                              aff, &p1);
+                                              type, &p1);
                nLower = 0;
        }
        if (pUpper && rc == SQLITE_OK) {
                rc = sqlite3Stat4ValueFromExpr(pParse, pUpper->pExpr->pRight,
-                                              aff, &p2);
+                                              type, &p2);
                nUpper = p2 ? 0 : index->def->opts.stat->sample_count;
        }
 
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index b124a1d53..efbc91cf8 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -364,7 +364,7 @@ disableTerm(WhereLevel * pLevel, WhereTerm * pTerm)
 }
 
 /*
- * Code an OP_Affinity opcode to apply the column affinity string zAff
+ * Code an OP_ApplyType opcode to apply the column type string types
  * to the n registers starting at base.
  *
  * As an optimization, AFFINITY_BLOB entries (which are no-ops) at the
@@ -396,9 +396,12 @@ codeApplyAffinity(Parse * pParse, int base, int n, char *zAff)
                n--;
        }
 
-       /* Code the OP_Affinity opcode if there is anything left to do. */
        if (n > 0) {
-               sqlite3VdbeAddOp4(v, OP_Affinity, base, n, 0, zAff, n);
+               enum field_type *types =
+                       sql_affinity_str_to_field_type_str(zAff);
+               types[n] = field_type_MAX;
+               sqlite3VdbeAddOp4(v, OP_ApplyType, base, n, 0, (char *)types,
+                                 P4_DYNAMIC);
                sqlite3ExprCacheAffinityChange(pParse, base, n);
        }
 }
diff --git a/test/sql-tap/cast.test.lua b/test/sql-tap/cast.test.lua
index 51d557937..189ca8933 100755
--- a/test/sql-tap/cast.test.lua
+++ b/test/sql-tap/cast.test.lua
@@ -70,7 +70,7 @@ test:do_catchsql_test(
         SELECT CAST(x'616263' AS numeric)
     ]], {
         -- <cast-1.5>
-        1, 'Type mismatch: can not convert abc to real'
+        1, 'Type mismatch: can not convert abc to number'
         -- </cast-1.5>
     })
 
@@ -450,7 +450,7 @@ test:do_catchsql_test(
         SELECT CAST('123abc' AS numeric)
     ]], {
         -- <cast-1.45>
-        1, 'Type mismatch: can not convert 123abc to real'
+        1, 'Type mismatch: can not convert 123abc to number'
         -- </cast-1.45>
     })
 
@@ -480,7 +480,7 @@ test:do_catchsql_test(
         SELECT CAST('123.5abc' AS numeric)
     ]], {
         -- <cast-1.51>
-        1, 'Type mismatch: can not convert 123.5abc to real'
+        1, 'Type mismatch: can not convert 123.5abc to number'
         -- </cast-1.51>
     })
 
@@ -561,7 +561,7 @@ test:do_catchsql_test(
         SELECT CAST('abc' AS REAL)
     ]], {
         -- <case-1.66>
-        1, 'Type mismatch: can not convert abc to real'
+        1, 'Type mismatch: can not convert abc to number'
         -- </case-1.66>
     })
 
@@ -875,7 +875,7 @@ test:do_test(
         ]]
     end, {
         -- <cast-4.4>
-        1, 'Type mismatch: can not convert abc to real'
+        1, 'Type mismatch: can not convert abc to number'
         -- </cast-4.4>
     })
 
diff --git a/test/sql-tap/tkt-80e031a00f.test.lua b/test/sql-tap/tkt-80e031a00f.test.lua
index 2d4f81798..8517a581f 100755
--- a/test/sql-tap/tkt-80e031a00f.test.lua
+++ b/test/sql-tap/tkt-80e031a00f.test.lua
@@ -346,7 +346,7 @@ test:do_catchsql_test(
         SELECT 'hello' IN t1
     ]], {
         -- <tkt-80e031a00f.27>
-        1, 'Type mismatch: can not convert hello to real'
+        1, 'Type mismatch: can not convert hello to number'
         -- </tkt-80e031a00f.27>
     })
 
@@ -356,7 +356,7 @@ test:do_catchsql_test(
         SELECT 'hello' NOT IN t1
     ]], {
         -- <tkt-80e031a00f.28>
-        1, 'Type mismatch: can not convert hello to real'
+        1, 'Type mismatch: can not convert hello to number'
         -- </tkt-80e031a00f.28>
     })
 
@@ -386,7 +386,7 @@ test:do_catchsql_test(
         SELECT x'303132' IN t1
     ]], {
         -- <tkt-80e031a00f.31>
-        1, 'Type mismatch: can not convert 012 to real'
+        1, 'Type mismatch: can not convert 012 to number'
         -- </tkt-80e031a00f.31>
     })
 
@@ -396,7 +396,7 @@ test:do_catchsql_test(
         SELECT x'303132' NOT IN t1
     ]], {
         -- <tkt-80e031a00f.32>
-        1, 'Type mismatch: can not convert 012 to real'
+        1, 'Type mismatch: can not convert 012 to number'
         -- </tkt-80e031a00f.32>
     })

  reply	other threads:[~2019-01-16 14:26 UTC|newest]

Thread overview: 48+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-12-28  9:34 [tarantool-patches] [PATCH 0/8] Eliminate affinity from source code Nikita Pettik
2018-12-28  9:34 ` [tarantool-patches] [PATCH 1/8] sql: remove SQLITE_ENABLE_UPDATE_DELETE_LIMIT define Nikita Pettik
2018-12-29 17:42   ` [tarantool-patches] " Vladislav Shpilevoy
2019-01-16 14:25     ` n.pettik
2018-12-28  9:34 ` [tarantool-patches] [PATCH 2/8] sql: use field type instead of affinity for type_def Nikita Pettik
2018-12-29 17:42   ` [tarantool-patches] " Vladislav Shpilevoy
2019-01-16 14:26     ` n.pettik
2018-12-28  9:34 ` [tarantool-patches] [PATCH 3/8] sql: remove numeric affinity Nikita Pettik
2018-12-29  9:01   ` [tarantool-patches] " Konstantin Osipov
2018-12-29 17:42   ` Vladislav Shpilevoy
2019-01-09  8:26     ` Konstantin Osipov
2019-01-16 14:26     ` n.pettik
2019-01-22 15:41       ` Vladislav Shpilevoy
2019-01-28 16:39         ` n.pettik
2019-01-30 13:04           ` Vladislav Shpilevoy
2019-02-01 16:39             ` n.pettik
2019-01-09  8:20   ` Konstantin Osipov
2018-12-28  9:34 ` [tarantool-patches] [PATCH 4/8] sql: replace affinity with field type for func Nikita Pettik
2018-12-28  9:34 ` [tarantool-patches] [PATCH 5/8] sql: replace field type with affinity for VDBE runtime Nikita Pettik
2018-12-29 17:42   ` [tarantool-patches] " Vladislav Shpilevoy
2019-01-16 14:26     ` n.pettik [this message]
2019-01-22 15:41       ` Vladislav Shpilevoy
2019-01-28 16:39         ` n.pettik
2019-01-30 13:04           ` Vladislav Shpilevoy
2019-02-01 16:39             ` n.pettik
2019-02-05 15:08               ` Vladislav Shpilevoy
2019-02-05 17:46                 ` n.pettik
2018-12-28  9:34 ` [tarantool-patches] [PATCH 6/8] sql: replace affinity with field type in struct Expr Nikita Pettik
2018-12-29 17:42   ` [tarantool-patches] " Vladislav Shpilevoy
2019-01-16 14:26     ` n.pettik
2019-01-22 15:41       ` Vladislav Shpilevoy
2019-01-28 16:39         ` n.pettik
2019-01-30 13:04           ` Vladislav Shpilevoy
2019-02-01 16:39             ` n.pettik
2019-02-05 15:08               ` Vladislav Shpilevoy
2019-02-05 17:46                 ` n.pettik
2018-12-28  9:34 ` [tarantool-patches] [PATCH 7/8] sql: clean-up affinity from SQL source code Nikita Pettik
2018-12-29 17:42   ` [tarantool-patches] " Vladislav Shpilevoy
2019-01-16 14:26     ` n.pettik
2019-01-22 15:41       ` Vladislav Shpilevoy
2019-01-28 16:40         ` n.pettik
2019-01-30 13:04           ` Vladislav Shpilevoy
2019-02-01 16:39             ` n.pettik
2019-02-05 15:08               ` Vladislav Shpilevoy
2019-02-05 17:46                 ` n.pettik
2018-12-28  9:34 ` [tarantool-patches] [PATCH 8/8] Remove affinity from field definition Nikita Pettik
2019-02-05 19:41 ` [tarantool-patches] Re: [PATCH 0/8] Eliminate affinity from source code Vladislav Shpilevoy
2019-02-08 13:37 ` Kirill Yukhin

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=DD9F7BFD-719D-464A-910E-2E5E01AD0F00@tarantool.org \
    --to=korablev@tarantool.org \
    --cc=tarantool-patches@freelists.org \
    --cc=v.shpilevoy@tarantool.org \
    --subject='[tarantool-patches] Re: [PATCH 5/8] sql: replace field type with affinity for VDBE runtime' \
    /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