[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