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

Kirill Shcherbatov kshcherbatov at tarantool.org
Thu May 23 13:19:36 MSK 2019


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              |  25 +++-
 src/box/sql.h              |  46 ++++++
 src/box/sql/tarantoolInt.h |   5 +-
 src/box/sql/vdbe.c         | 287 ++++++++++++++++++++++---------------
 src/box/sql/vdbeInt.h      |  11 +-
 5 files changed, 239 insertions(+), 135 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index 385676055..7f581aad8 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -182,18 +182,15 @@ const void *tarantoolsqlPayloadFetch(BtCursor *pCur, u32 *pAmt)
 }
 
 const void *
-tarantoolsqlTupleColumnFast(BtCursor *pCur, u32 fieldno, u32 *field_size)
+tarantool_tuple_field_fast(struct tuple *tuple, uint32_t fieldno,
+			   uint32_t *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);
+	struct tuple_format *format = tuple_format(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 *field = tuple_field(tuple, fieldno);
 	const char *end = field;
 	mp_next(&end);
 	*field_size = end - field;
@@ -1362,3 +1359,17 @@ sql_checks_resolve_space_def_reference(ExprList *expr_list,
 	sql_parser_destroy(&parser);
 	return rc;
 }
+
+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;
+}
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.
+ * 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 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).
+ */
+void
+tuple_fetcher_create(struct tuple_fetcher *fetcher, struct tuple *tuple,
+		     const char *data, uint32_t data_sz);
 
 #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
@@ -20,7 +20,7 @@ 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 tuple Tuple to process.
  * @param fieldno Number of a field to get.
  * @param[out] field_size Result field size.
  * @retval not NULL MessagePack field.
@@ -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);
 
 int tarantoolsqlFirst(BtCursor * pCur, int *pRes);
 int tarantoolsqlLast(BtCursor * pCur, int *pRes);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 5d37f63fb..33c5458cd 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -612,6 +612,119 @@ mem_type_to_str(const struct Mem *p)
 	}
 }
 
+/**
+ * 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 field_type The destination memory field type is used
+ *                   when fetch result type is undefined.
+ * @param default_val_mem The value to return when fetcher's data
+ *                        lacks field by given @a field_idx.
+ * @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,
+		    enum field_type field_type, struct Mem *default_val_mem,
+		    struct Mem *dest_mem)
+{
+	sqlVdbeMemSetNull(dest_mem);
+	uint32_t *slots = fetcher->slots;
+
+	if (field_idx >= fetcher->field_count) {
+		if (default_val_mem != NULL) {
+			sqlVdbeMemShallowCopy(dest_mem, default_val_mem,
+					      MEM_Static);
+		}
+		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;
+		if (fetcher->tuple != NULL &&
+		    (data = tarantool_tuple_field_fast(fetcher->tuple,
+						       field_idx,
+						       &field_sz)) != 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;
+	}
+	if ((dest_mem->flags & MEM_Int) != 0) {
+		if (field_type == FIELD_TYPE_NUMBER)
+			sqlVdbeMemSetDouble(dest_mem, dest_mem->u.i);
+	}
+	/*
+	 * 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;
+	}
+	return SQL_OK;
+}
+
 /*
  * Execute as much of a VDBE program as we can.
  * This is the core of sql_step().
@@ -2597,12 +2710,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,19 +2722,21 @@ 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);
 
 	if (pC->cacheStatus!=p->cacheCtr) {                /*OPTIMIZATION-IF-FALSE*/
+		struct tuple *tuple = NULL;
+		uint32_t data_sz;
+		const char *data;
 		if (pC->nullRow) {
 			if (pC->eCurType==CURTYPE_PSEUDO) {
 				assert(pC->uc.pseudoTableReg>0);
 				pReg = &aMem[pC->uc.pseudoTableReg];
 				assert(pReg->flags & MEM_Blob);
 				assert(memIsValid(pReg));
-				pC->szRow = pReg->n;
-				pC->aRow = (u8*)pReg->z;
+				data_sz = pReg->n;
+				data = pReg->z;
 			} else {
 				sqlVdbeMemSetNull(pDest);
 				goto op_column_out;
@@ -2638,134 +2748,73 @@ case OP_Column: {
 			assert(sqlCursorIsValid(pCrsr));
 			assert(pCrsr->curFlags & BTCF_TaCursor ||
 			       pCrsr->curFlags & BTCF_TEphemCursor);
-			pC->aRow = tarantoolsqlPayloadFetch(pCrsr, &pC->szRow);
-
+			tuple = pCrsr->last_tuple;
+			data = tarantoolsqlPayloadFetch(pCrsr, &data_sz);
 		}
 		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;
+		tuple_fetcher_create(&pC->fetcher, tuple, data, data_sz);
 	}
-	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, field_type,
+			       default_val_mem, pDest);
+	if (rc != SQL_OK)
+		goto abort_due_to_error;
 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 the data that P1 points as an initialized
+ * tuple_fetcher object.
+ *
+ * Fetch the P2-th column from their tuple. The value extracted
+ * is stored in register P3.
+ *
+ * If the column contains fewer than P2 fields, then extract
+ * a NULL. Or, if the P4 argument is a P4_MEM use the value of
+ * the P4 argument as the result.
+ *
+ * Value of P5 register is an 'expected' destination value type.
+ */
+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];
+	struct Mem *default_val_mem =
+		pOp->p4type == P4_MEM ? pOp->p4.pMem : NULL;
+	enum field_type field_type = pOp->p5;
+	memAboutToChange(p, dest_mem);
+	rc = tuple_fetcher_fetch(fetcher, field_idx, field_type,
+			         default_val_mem, dest_mem);
+	if (rc != SQL_OK)
+		goto abort_due_to_error;
+	UPDATE_MAX_BLOBSIZE(dest_mem);
+	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..cb51446a8 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
+	/**
+	 * Auxilary 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