From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTP id 83B482E603 for ; Thu, 23 May 2019 06:19:43 -0400 (EDT) Received: from turing.freelists.org ([127.0.0.1]) by localhost (turing.freelists.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id tI9bIiUiHyt8 for ; Thu, 23 May 2019 06:19:43 -0400 (EDT) Received: from smtp36.i.mail.ru (smtp36.i.mail.ru [94.100.177.96]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTPS id AAE1A2E49D for ; Thu, 23 May 2019 06:19:42 -0400 (EDT) From: Kirill Shcherbatov Subject: [tarantool-patches] [PATCH v5 3/6] sql: introduce tuple_fetcher class Date: Thu, 23 May 2019 13:19:36 +0300 Message-Id: In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Sender: tarantool-patches-bounce@freelists.org Errors-to: tarantool-patches-bounce@freelists.org Reply-To: tarantool-patches@freelists.org List-Help: List-Unsubscribe: List-software: Ecartis version 1.0.0 List-Id: tarantool-patches List-Subscribe: List-Owner: List-post: List-Archive: To: tarantool-patches@freelists.org, v.shpilevoy@tarantool.org Cc: Kirill Shcherbatov 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->p1nCursor); assert(pC!=0); assert(p2nField); - 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->szMallocz = 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@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