[tarantool-patches] Re: [PATCH v5 3/6] sql: introduce tuple_fetcher class

Kirill Shcherbatov kshcherbatov at tarantool.org
Fri May 31 16:45:31 MSK 2019


Hi! Thank you for review.

On 26.05.2019 15:05, Vladislav Shpilevoy wrote:
> Hi! Thanks for the fixes! See 10 comments below.
> 
>> diff --git a/src/box/sql.h b/src/box/sql.h
>> index 15ef74b19..3aaeb2274 100644
>> --- a/src/box/sql.h
>> +++ b/src/box/sql.h
>> @@ -386,6 +386,52 @@ sql_src_list_entry_name(const struct SrcList *list, int i);
>>  void
>>  sqlSrcListDelete(struct sql *db, struct SrcList *list);
>>  
>> +/**
>> + * Auxilary VDBE structure to speed-up tuple data field access.
> 
> 1. 'Auxilary' -> 'Auxiliary'. Looks like you still did not install
> spell checker, as I asked in some previous patchsets. Please, do it.
Done. I use spell checker.  It is unobtrusive. Moreover, this word exists in
this meaning. Doesn't matter. Fixed.
  

>> +void
>> +tuple_fetcher_create(struct tuple_fetcher *fetcher, struct tuple *tuple,
>> +		     const char *data, uint32_t data_sz);
> 
> 2.>...
> Please, expose two public functions: tuple_fetcher_prepare_data and
> tuple_fetcher_prepare_tuple. Each takes either const char *data or
> struct tuple *tuple. Internally they call tuple_fetcher_create with
> both tuple and data.
>...
Done

> 
>>  
>>  #if defined(__cplusplus)
>>  } /* extern "C" { */
>> diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
>> index 2b04d961e..bb0f8f1a4 100644
>> --- a/src/box/sql/tarantoolInt.h
>> +++ b/src/box/sql/tarantoolInt.h
>> @@ -28,7 +28,8 @@ const void *tarantoolsqlPayloadFetch(BtCursor * pCur, u32 * pAmt);
>>   *         offset to @a fieldno.
>>   */
>>  const void *
>> -tarantoolsqlTupleColumnFast(BtCursor *pCur, u32 fieldno, u32 *field_size);
>> +tarantool_tuple_field_fast(struct tuple *tuple, uint32_t fieldno,
>> +			   uint32_t *field_size);
> 
> 3. I suggest you to make this function method of the fetcher and static inline
> right above tuple_fetcher_fetch(). It is not needed in any other place.
Done.

> 4. The only reason to make tuple_fetcher_fetch() a separate function
> was to get rid of this parameter and related code, keeping it in
> OP_Column. Please, do it. Remove the parameter and keep it in
> OP_Column.

> 5. Why would ever need that parameter for OP_Fetch? It is not a
> task of a fetcher to return default fields IMO. It should only fetch
> a field, not replace it with a default value.

static int
tuple_fetcher_fetch(struct tuple_fetcher *fetcher, uint32_t field_idx,
		    struct Mem *dest_mem)

	rc = tuple_fetcher_fetch(&pC->fetcher, p2, pDest);
	if (rc != SQL_OK)
		goto abort_due_to_error;

	if ((pDest->flags & MEM_Null) &&
	    (uint32_t) p2  >= pC->fetcher.field_count &&
	    default_val_mem != NULL) {
		sqlVdbeMemShallowCopy(pDest, default_val_mem, MEM_Static);
	}
	if ((pDest->flags & MEM_Int) != 0) {
		if (field_type == FIELD_TYPE_NUMBER)
			sqlVdbeMemSetDouble(pDest, pDest->u.i);
	}

>> + * Interpret the data that P1 points as an initialized
> 
> 6. "Interpret data P1 points at as an initialized ...".
Fixed.

>> + * Fetch the P2-th column from their tuple. The value extracted
> 
> 7. 'Their' is for plural. Use 'its'.
Fixed.

> 8. You never generate OP_Fetch with a default value. Please,
> drop P4 argument. As well as P5. They are never used, even in the
> last patch.
> 
>> + *
>> + * Value of P5 register is an 'expected' destination value type.
> 
> 9. It is not 'expected'. I can't pass here FIELD_TYPE_STRING and get
> a string value despite an actually stored type. It is rather a flag,
> that you need to transform 'int' into 'number'. Anyway, it should be
> dropped.
Done.


> 10. Why is not this command a part of tuple_fetcher_fetch()?
Don't mind. Done.

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

Refactored OP_Column instruction with a new tuple_fetcher class.
The tuple_fetcher is a reusable object that speed-up field access
for given tuple.
Introduced OP_Fetch opcode that uses tuple_fetcher given as a
first argument. This opcode makes possible to perform binding
of a new tuple to an existent VDBE without decoding it's fields.

Needed for #3691
---
 src/box/sql.c              |  54 ++++---
 src/box/sql.h              |  54 +++++++
 src/box/sql/tarantoolInt.h |  12 --
 src/box/sql/vdbe.c         | 296 ++++++++++++++++++++++---------------
 src/box/sql/vdbeInt.h      |  11 +-
 5 files changed, 270 insertions(+), 157 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index 385676055..07ceec2d2 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -181,25 +181,6 @@ const void *tarantoolsqlPayloadFetch(BtCursor *pCur, u32 *pAmt)
 	return tuple_data(pCur->last_tuple);
 }
 
-const void *
-tarantoolsqlTupleColumnFast(BtCursor *pCur, u32 fieldno, u32 *field_size)
-{
-	assert(pCur->curFlags & BTCF_TaCursor ||
-	       pCur->curFlags & BTCF_TEphemCursor);
-	assert(pCur->last_tuple != NULL);
-
-	struct tuple_format *format = tuple_format(pCur->last_tuple);
-	if (fieldno >= tuple_format_field_count(format) ||
-	    tuple_format_field(format, fieldno)->offset_slot ==
-	    TUPLE_OFFSET_SLOT_NIL)
-		return NULL;
-	const char *field = tuple_field(pCur->last_tuple, fieldno);
-	const char *end = field;
-	mp_next(&end);
-	*field_size = end - field;
-	return field;
-}
-
 /*
  * Set cursor to the first tuple in given space.
  * It is a simple wrapper around cursor_seek().
@@ -1362,3 +1343,38 @@ sql_checks_resolve_space_def_reference(ExprList *expr_list,
 	sql_parser_destroy(&parser);
 	return rc;
 }
+
+/**
+ * Initialize a new tuple_fetcher instance with given tuple
+ * data.
+ * @param fetcher The tuple_fetcher instance to initialize.
+ * @param tuple The tuple object pointer or NULL when undefined.
+ * @param data The tuple data (is always defined).
+ * @param data_sz The size of tuple data (is always defined).
+ */
+static void
+tuple_fetcher_create(struct tuple_fetcher *fetcher, struct tuple *tuple,
+		     const char *data, uint32_t data_sz)
+{
+	fetcher->tuple = tuple;
+	fetcher->data = data;
+	fetcher->data_sz = data_sz;
+
+	const char *field0 = data;
+	fetcher->field_count = mp_decode_array((const char **) &field0);
+	fetcher->slots[0] = (uint32_t)(field0 - data);
+	fetcher->rightmost_slot = 0;
+}
+
+void
+tuple_fetcher_prepare_data(struct tuple_fetcher *fetcher, const char *data,
+			   uint32_t data_sz)
+{
+	tuple_fetcher_create(fetcher, NULL, data, data_sz);
+}
+
+void
+tuple_fetcher_prepare_tuple(struct tuple_fetcher *fetcher, struct tuple *tuple)
+{
+	tuple_fetcher_create(fetcher, tuple, tuple_data(tuple), tuple->bsize);
+}
diff --git a/src/box/sql.h b/src/box/sql.h
index 15ef74b19..a13f78ae8 100644
--- a/src/box/sql.h
+++ b/src/box/sql.h
@@ -386,6 +386,60 @@ sql_src_list_entry_name(const struct SrcList *list, int i);
 void
 sqlSrcListDelete(struct sql *db, struct SrcList *list);
 
+/**
+ * Auxiliary VDBE structure to speed-up tuple data field access.
+ * A memory allocation that manage this structure must have
+ * trailing unused bytes that extends the last 'slots' array.
+ * The amount of reserved memory should correspond to the problem
+ * to be solved and is usually equal to the greatest number of
+ * fields in the tuple.
+ *
+ * +------------------------+
+ * |  struct tuple_fetcher  |
+ * +------------------------+
+ * |     RESERVED MEMORY    |
+ * +------------------------+
+ */
+struct tuple_fetcher {
+	/** Tuple pointer or NULL when undefined. */
+	struct tuple *tuple;
+	/** Tuple data pointer. */
+	const char *data;
+	/** Tuple data size. */
+	uint32_t data_sz;
+	/** Count of fields in tuple. */
+	uint32_t field_count;
+	/**
+	 * Index of the rightmost initialized slot in slots
+	 * array.
+	 */
+	uint32_t rightmost_slot;
+	/**
+	 * Array of offsets of tuple fields.
+	 * Only values <= rightmost_slot are valid.
+	 */
+	uint32_t slots[1];
+};
+
+/**
+ * Initialize a new tuple_fetcher instance with given tuple
+ * data.
+ * @param fetcher The tuple_fetcher instance to initialize.
+ * @param data The tuple data.
+ * @param data_sz The size of tuple data.
+ */
+void
+tuple_fetcher_prepare_data(struct tuple_fetcher *fetcher, const char *data,
+			   uint32_t data_sz);
+
+/**
+ * Initialize a new tuple_fetcher instance with given tuple
+ * data.
+ * @param fetcher The tuple_fetcher instance to initialize.
+ * @param tuple The tuple object pointer.
+ */
+void
+tuple_fetcher_prepare_tuple(struct tuple_fetcher *fetcher, struct tuple *tuple);
 
 #if defined(__cplusplus)
 } /* extern "C" { */
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index 2b04d961e..2dd7eef20 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -18,18 +18,6 @@ int is_tarantool_error(int rc);
 /* Storage interface. */
 const void *tarantoolsqlPayloadFetch(BtCursor * pCur, u32 * pAmt);
 
-/**
- * Try to get a current tuple field using its field map.
- * @param pCur Btree cursor holding a tuple.
- * @param fieldno Number of a field to get.
- * @param[out] field_size Result field size.
- * @retval not NULL MessagePack field.
- * @retval     NULL Can not use field_map - it does not contain
- *         offset to @a fieldno.
- */
-const void *
-tarantoolsqlTupleColumnFast(BtCursor *pCur, u32 fieldno, u32 *field_size);
-
 int tarantoolsqlFirst(BtCursor * pCur, int *pRes);
 int tarantoolsqlLast(BtCursor * pCur, int *pRes);
 int tarantoolsqlNext(BtCursor * pCur, int *pRes);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 91830813d..250646c9b 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -43,6 +43,7 @@
 #include "box/error.h"
 #include "box/fk_constraint.h"
 #include "box/txn.h"
+#include "box/tuple.h"
 #include "sqlInt.h"
 #include "vdbeInt.h"
 #include "tarantoolInt.h"
@@ -612,6 +613,133 @@ mem_type_to_str(const struct Mem *p)
 	}
 }
 
+/**
+ * Try to get a current tuple field using its field map.
+ * @param tuple Tuple to process.
+ * @param fieldno Number of a field to get.
+ * @param[out] field_size Result field size.
+ * @retval not NULL MessagePack field.
+ * @retval NULL Can not use field_map - it does not contain
+ *              offset to @a fieldno.
+ */
+static const void *
+tuple_fetcher_tuple_field_fast(struct tuple_fetcher *fetcher, uint32_t fieldno,
+			       uint32_t *field_size)
+{
+	if (fetcher->tuple == NULL)
+		return NULL;
+	struct tuple_format *format = tuple_format(fetcher->tuple);
+	if (fieldno >= tuple_format_field_count(format) ||
+	    tuple_format_field(format, fieldno)->offset_slot ==
+	    TUPLE_OFFSET_SLOT_NIL)
+		return NULL;
+	const char *field = tuple_field(fetcher->tuple, fieldno);
+	const char *end = field;
+	mp_next(&end);
+	*field_size = end - field;
+	return field;
+}
+
+/**
+ * Fetch field by field_idx using tuple_fetcher and store result
+ * in dest_mem.
+ * @param fetcher The initialized tuple_fetcher instance to use.
+ * @param field_idx The id of the field to fetch.
+ * @param[out] dest_mem The memory variable to store result.
+ * @retval SQL_OK Status code in case of success.
+ * @retval sql_ret_code Error code otherwise.
+ */
+static int
+tuple_fetcher_fetch(struct tuple_fetcher *fetcher, uint32_t field_idx,
+		    struct Mem *dest_mem)
+{
+	sqlVdbeMemSetNull(dest_mem);
+	uint32_t *slots = fetcher->slots;
+	if (field_idx >= fetcher->field_count) {
+		UPDATE_MAX_BLOBSIZE(dest_mem);
+		return SQL_OK;
+	}
+
+	/*
+	 * Make sure at least the first field_idx + 1 entries
+	 * of the header have been parsed and valid information
+	 * is in field_map[].
+	 * If there is more header available for parsing in the
+	 * record, try to extract additional fields up through the
+	 * field_map+1-th field.
+	 */
+	const char *data;
+	if (fetcher->rightmost_slot <= field_idx) {
+		uint32_t field_sz;
+		data = tuple_fetcher_tuple_field_fast(fetcher, field_idx,
+						      &field_sz);
+		if (data != NULL) {
+			/*
+			 * Special case for tarantool spaces: for
+			 * indexed fields a tuple field map can be
+			 * used. Else there is no sense in
+			 * tuple_field usage, because it makes
+			 * foreach field { mp_next(); } to find
+			 * a field. In such a case sql is
+			 * better - it saves offsets to all fields
+			 * visited in mp_next() cycle.
+			 */
+			uint32_t offset = (uint32_t)(data - fetcher->data);
+			slots[field_idx] = offset;
+			slots[field_idx + 1] = offset + field_sz;
+		} else {
+			uint32_t i = fetcher->rightmost_slot;
+			data = fetcher->data + slots[i];
+			do {
+				mp_next(&data);
+				slots[++i] = (uint32_t)(data - fetcher->data);
+			} while (i <= field_idx);
+			fetcher->rightmost_slot = i;
+		}
+	}
+
+	/*
+	 * Extract the content for the p2+1-th column. Control
+	 * can only reach this point if field_map[field_idx],
+	 * field_map[field_idx+1] are valid.
+	 */
+	assert(sqlVdbeCheckMemInvariants(dest_mem) != 0);
+	uint32_t dummy;
+	data = fetcher->data + slots[field_idx];
+	if (vdbe_decode_msgpack_into_mem(data, dest_mem, &dummy) != 0)
+		return SQL_TARANTOOL_ERROR;
+
+	/*
+	 * MsgPack map, array or extension (unsupported in sql).
+	 * Wrap it in a blob verbatim.
+	 */
+	if (dest_mem->flags == 0) {
+		dest_mem->n = slots[field_idx + 1] - slots[field_idx];
+		dest_mem->z = (char *) data;
+		dest_mem->flags = MEM_Blob | MEM_Ephem | MEM_Subtype;
+		dest_mem->subtype = SQL_SUBTYPE_MSGPACK;
+	}
+	/*
+	 * Add 0 termination (at most for strings)
+	 * Not sure why do we check MEM_Ephem
+	 */
+	if ((dest_mem->flags & (MEM_Ephem | MEM_Str)) == (MEM_Ephem | MEM_Str)) {
+		int len = dest_mem->n;
+		if (dest_mem->szMalloc < len + 1) {
+			if (sqlVdbeMemGrow(dest_mem, len + 1, 1) != 0)
+				return SQL_NOMEM;
+		} else {
+			dest_mem->z =
+				memcpy(dest_mem->zMalloc, dest_mem->z, len);
+			dest_mem->flags &= ~MEM_Ephem;
+		}
+		dest_mem->z[len] = 0;
+		dest_mem->flags |= MEM_Term;
+	}
+	UPDATE_MAX_BLOBSIZE(dest_mem);
+	return SQL_OK;
+}
+
 /*
  * Execute as much of a VDBE program as we can.
  * This is the core of sql_step().
@@ -2597,12 +2725,7 @@ case OP_Column: {
 	int p2;            /* column number to retrieve */
 	VdbeCursor *pC;    /* The VDBE cursor */
 	BtCursor *pCrsr = NULL; /* The BTree cursor */
-	u32 *aOffset;      /* aOffset[i] is offset to start of data for i-th column */
-	int i;             /* Loop counter */
 	Mem *pDest;        /* Where to write the extracted value */
-	const u8 *zData;   /* Part of the record being decoded */
-	const u8 MAYBE_UNUSED *zEnd;    /* Data end */
-	const u8 *zParse;  /* Next unparsed byte of the row */
 	Mem *pReg;         /* PseudoTable input register */
 
 	pC = p->apCsr[pOp->p1];
@@ -2614,7 +2737,6 @@ case OP_Column: {
 	assert(pOp->p1>=0 && pOp->p1<p->nCursor);
 	assert(pC!=0);
 	assert(p2<pC->nField);
-	aOffset = pC->aOffset;
 	assert(pC->eCurType!=CURTYPE_PSEUDO || pC->nullRow);
 	assert(pC->eCurType!=CURTYPE_SORTER);
 
@@ -2625,8 +2747,8 @@ case OP_Column: {
 				pReg = &aMem[pC->uc.pseudoTableReg];
 				assert(pReg->flags & MEM_Blob);
 				assert(memIsValid(pReg));
-				pC->szRow = pReg->n;
-				pC->aRow = (u8*)pReg->z;
+				tuple_fetcher_prepare_data(&pC->fetcher,
+							   pReg->z, pReg->n);
 			} else {
 				sqlVdbeMemSetNull(pDest);
 				goto op_column_out;
@@ -2638,134 +2760,70 @@ case OP_Column: {
 			assert(sqlCursorIsValid(pCrsr));
 			assert(pCrsr->curFlags & BTCF_TaCursor ||
 			       pCrsr->curFlags & BTCF_TEphemCursor);
-			pC->aRow = tarantoolsqlPayloadFetch(pCrsr, &pC->szRow);
-
+			tuple_fetcher_prepare_tuple(&pC->fetcher,
+						    pCrsr->last_tuple);
 		}
 		pC->cacheStatus = p->cacheCtr;
-		zParse = pC->aRow;
-		pC->nRowField = mp_decode_array((const char **)&zParse); /* # of fields */
-		aOffset[0] = (u32)(zParse - pC->aRow);
-		pC->nHdrParsed = 0;
-	}
-
-	if ( (u32)p2>=pC->nRowField) {
-		if (pOp->p4type==P4_MEM) {
-			sqlVdbeMemShallowCopy(pDest, pOp->p4.pMem, MEM_Static);
-		} else {
-			sqlVdbeMemSetNull(pDest);
-		}
-		goto op_column_out;
 	}
-	zData = pC->aRow;
-	zEnd = zData + pC->szRow;
-
-	/*
-	 * Make sure at least the first p2+1 entries of the header
-	 * have been parsed and valid information is in aOffset[].
-	 * If there is more header available for parsing in the
-	 * record, try to extract additional fields up through the
-	 * p2+1-th field.
-	 */
-	if (pC->nHdrParsed <= p2) {
-		u32 size;
-		if (pC->eCurType == CURTYPE_TARANTOOL &&
-		    pCrsr != NULL && ((pCrsr->curFlags & BTCF_TaCursor) != 0 ||
-		    (pCrsr->curFlags & BTCF_TEphemCursor)) &&
-		    (zParse = tarantoolsqlTupleColumnFast(pCrsr, p2,
-							      &size)) != NULL) {
-			/*
-			 * Special case for tarantool spaces: for
-			 * indexed fields a tuple field map can be
-			 * used. Else there is no sense in
-			 * tuple_field usage, because it makes
-			 * foreach field { mp_next(); } to find
-			 * a field. In such a case sql is
-			 * better - it saves offsets to all fields
-			 * visited in mp_next() cycle.
-			 */
-			aOffset[p2] = zParse - zData;
-			aOffset[p2 + 1] = aOffset[p2] + size;
-		} else {
-			i = pC->nHdrParsed;
-			zParse = zData+aOffset[i];
-
-			/*
-			 * Fill in aOffset[i] values through the
-			 * p2-th field.
-			 */
-			do{
-				mp_next((const char **) &zParse);
-				aOffset[++i] = (u32)(zParse-zData);
-			}while( i<=p2);
-			assert((u32)p2 != pC->nRowField || zParse == zEnd);
-			pC->nHdrParsed = i;
-		}
-	}
-
-	/* Extract the content for the p2+1-th column.  Control can only
-	 * reach this point if aOffset[p2], aOffset[p2+1] are
-	 * all valid.
-	 */
-	assert(rc==SQL_OK);
-	assert(sqlVdbeCheckMemInvariants(pDest));
-	if (VdbeMemDynamic(pDest)) {
-		sqlVdbeMemSetNull(pDest);
-	}
-	uint32_t unused;
-	if (vdbe_decode_msgpack_into_mem((const char *)(zData + aOffset[p2]),
-					 pDest, &unused) != 0) {
-		rc = SQL_TARANTOOL_ERROR;
-		goto abort_due_to_error;
-	}
-	/* MsgPack map, array or extension (unsupported in sql).
-	 * Wrap it in a blob verbatim.
-	 */
-
-	if (pDest->flags == 0) {
-		pDest->n = aOffset[p2+1]-aOffset[p2];
-		pDest->z = (char *)zData+aOffset[p2];
-		pDest->flags = MEM_Blob|MEM_Ephem|MEM_Subtype;
-		pDest->subtype = SQL_SUBTYPE_MSGPACK;
-	}
-	if ((pDest->flags & MEM_Int) != 0 &&
-	    pC->eCurType == CURTYPE_TARANTOOL) {
-		enum field_type f = FIELD_TYPE_ANY;
+	enum field_type field_type = FIELD_TYPE_ANY;
+	if (pC->eCurType == CURTYPE_TARANTOOL) {
 		/*
 		 * Ephemeral spaces feature only one index
 		 * covering all fields, but ephemeral spaces
 		 * lack format. So, we can fetch type from
 		 * key parts.
 		 */
-		if (pC->uc.pCursor->curFlags & BTCF_TEphemCursor)
-			f = pC->uc.pCursor->index->def->key_def->parts[p2].type;
-		else if (pC->uc.pCursor->curFlags & BTCF_TaCursor)
-			f = pC->uc.pCursor->space->def->fields[p2].type;
-		if (f == FIELD_TYPE_NUMBER)
-			sqlVdbeMemSetDouble(pDest, pDest->u.i);
-	}
-	/*
-	 * Add 0 termination (at most for strings)
-	 * Not sure why do we check MEM_Ephem
-	 */
-	if ((pDest->flags & (MEM_Ephem | MEM_Str)) == (MEM_Ephem | MEM_Str)) {
-		int len = pDest->n;
-		if (pDest->szMalloc<len+1) {
-			if (sqlVdbeMemGrow(pDest, len+1, 1))
-				goto abort_due_to_error;
-		} else {
-			pDest->z = memcpy(pDest->zMalloc, pDest->z, len);
-			pDest->flags &= ~MEM_Ephem;
+		if (pC->uc.pCursor->curFlags & BTCF_TEphemCursor) {
+			field_type = pC->uc.pCursor->index->def->
+					key_def->parts[p2].type;
+		} else if (pC->uc.pCursor->curFlags & BTCF_TaCursor) {
+			field_type = pC->uc.pCursor->space->def->
+					fields[p2].type;
 		}
-		pDest->z[len] = 0;
-		pDest->flags |= MEM_Term;
 	}
+	struct Mem *default_val_mem =
+		pOp->p4type == P4_MEM ? pOp->p4.pMem : NULL;
+	rc = tuple_fetcher_fetch(&pC->fetcher, p2, pDest);
+	if (rc != SQL_OK)
+		goto abort_due_to_error;
 
+	if ((pDest->flags & MEM_Null) &&
+	    (uint32_t) p2  >= pC->fetcher.field_count &&
+	    default_val_mem != NULL) {
+		sqlVdbeMemShallowCopy(pDest, default_val_mem, MEM_Static);
+	}
+	if ((pDest->flags & MEM_Int) != 0) {
+		if (field_type == FIELD_TYPE_NUMBER)
+			sqlVdbeMemSetDouble(pDest, pDest->u.i);
+	}
 op_column_out:
-	UPDATE_MAX_BLOBSIZE(pDest);
 	REGISTER_TRACE(p, pOp->p3, pDest);
 	break;
 }
 
+/* Opcode: Fetch P1 P2 P3 P4 P5
+ * Synopsis: r[P3]=PX
+ *
+ * Interpret data P1 points at as an initialized tuple_fetcher
+ * object.
+ *
+ * Fetch the P2-th column from its tuple. The value extracted
+ * is stored in register P3. If the column contains fewer than
+ * P2 fields, then extract a NULL.
+ */
+case OP_Fetch: {
+	struct tuple_fetcher *fetcher =
+		(struct tuple_fetcher *) p->aMem[pOp->p1].u.p;
+	uint32_t field_idx = pOp->p2;
+	struct Mem *dest_mem = &aMem[pOp->p3];
+	memAboutToChange(p, dest_mem);
+	rc = tuple_fetcher_fetch(fetcher, field_idx, dest_mem);
+	if (rc != SQL_OK)
+		goto abort_due_to_error;
+	REGISTER_TRACE(p, pOp->p3, dest_mem);
+	break;
+}
+
 /* Opcode: ApplyType P1 P2 * P4 *
  * Synopsis: type(r[P1 at P2])
  *
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index d58e3df7b..1a428afa9 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -112,17 +112,14 @@ struct VdbeCursor {
 	/** Info about keys needed by index cursors. */
 	struct key_def *key_def;
 	i16 nField;		/* Number of fields in the header */
-	u16 nHdrParsed;		/* Number of header fields parsed so far */
-	const u8 *aRow;		/* Data for the current row, if all on one page */
-	u32 szRow;		/* Byte available in aRow */
 #ifdef SQL_ENABLE_COLUMN_USED_MASK
 	u64 maskUsed;		/* Mask of columns used by this cursor */
 #endif
-	u32 nRowField;		/* Number of fields in the current row */
-	/* Offsets for all fields in the record [nField+1]
-	 * Order of fields is the same as it was passes to create table statement
+	/**
+	 * Auxiliary VDBE structure to speed-up tuple data
+	 * field access.
 	 */
-	u32 aOffset[1];
+	struct tuple_fetcher fetcher;
 };
 
 /*
-- 
2.21.0





More information about the Tarantool-patches mailing list