From: "n.pettik" <korablev@tarantool.org> To: tarantool-patches@freelists.org Cc: Kirill Shcherbatov <kshcherbatov@tarantool.org> Subject: [tarantool-patches] Re: [PATCH v1 3/3] sql: do not use OP_Delete+OP_Insert for UPDATES Date: Tue, 25 Dec 2018 19:26:31 +0200 [thread overview] Message-ID: <487E532B-E70C-4707-890A-3D68ADAE0929@tarantool.org> (raw) In-Reply-To: <7d8b0941a364a42f801f93e1526c1a6b244092e5.1545233551.git.kshcherbatov@tarantool.org> > @@ -253,15 +246,29 @@ fkey_lookup_parent(struct Parse *parse_context, struct space *parent, > } > sqlite3VdbeGoto(v, ok_label); > } > - 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); > - sqlite3VdbeAddOp4Int(v, OP_Found, cursor, ok_label, rec_reg, 0); > - sqlite3ReleaseTempReg(parse_context, rec_reg); > - sqlite3ReleaseTempRange(parse_context, temp_regs, field_count); Please, don’t change code without any explanations. You should state it within commit message or in source code. > + if (!(fkey_is_self_referenced(fk_def) && op == TK_UPDATE)) { > + int temp_regs = sqlite3GetTempRange(parse_context, field_count); > + int rec_reg = sqlite3GetTempReg(parse_context); > + vdbe_emit_open_cursor(parse_context, cursor, referenced_idx, > + parent); > + link = fk_def->links; > + for (uint32_t i = 0; i < field_count; ++i, ++link) { > + sqlite3VdbeAddOp2(v, OP_Copy, > + link->child_field + 1 + reg_data, > + temp_regs + i); > + } > + 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); > + sqlite3VdbeAddOp4Int(v, OP_Found, cursor, ok_label, rec_reg, 0); > + sqlite3ReleaseTempReg(parse_context, rec_reg); > + sqlite3ReleaseTempRange(parse_context, temp_regs, field_count); > + } > struct session *session = current_session(); > if (!fk_def->is_deferred && > (session->sql_flags & SQLITE_DeferFKs) == 0 && > @@ -515,7 +522,7 @@ fkey_action_is_set_null(struct Parse *parse_context, const struct fkey *fkey) > > void > fkey_emit_check(struct Parse *parser, struct Table *tab, int reg_old, > - int reg_new, const int *changed_cols) > + int reg_new, const int *changed_cols, int op) Why do you need additional argument? reg_new != 0 for insert reg_old != 0 for delete Changed_cols != 0 for update (According to comment to this function) What is more, according to code you need only case when update is processed. > { > struct sqlite3 *db = parser->db; > struct session *user_session = current_session(); > @@ -549,7 +556,7 @@ fkey_emit_check(struct Parse *parser, struct Table *tab, int reg_old, > * foreign key constraint violation. > */ > fkey_lookup_parent(parser, parent, fk_def, fk->index_id, > - reg_old, -1); > + reg_old, -1, op); > } > if (reg_new != 0 && !fkey_action_is_set_null(parser, fk)) { > /* > @@ -568,7 +575,7 @@ fkey_emit_check(struct Parse *parser, struct Table *tab, int reg_old, > * cause an FK violation. > */ > fkey_lookup_parent(parser, parent, fk_def, fk->index_id, > - reg_new, +1); > + reg_new, +1, op); > } > } > /* > > void > -vdbe_emit_insertion_completion(struct Vdbe *v, struct space *space, > - int raw_data_reg, uint32_t tuple_len, > +vdbe_emit_insertion_completion(struct Vdbe *v, struct space *space, int op, > + int raw_data_reg, int raw_key_reg, > + int upd_cols_reg, > enum on_conflict_action on_conflict) > { > assert(v != NULL); > @@ -1065,10 +1065,18 @@ vdbe_emit_insertion_completion(struct Vdbe *v, struct space *space, > pik_flags |= OPFLAG_OE_FAIL; > else if (on_conflict == ON_CONFLICT_ACTION_ROLLBACK) > pik_flags |= OPFLAG_OE_ROLLBACK; > - sqlite3VdbeAddOp3(v, OP_MakeRecord, raw_data_reg, tuple_len, > - raw_data_reg + tuple_len); > - sqlite3VdbeAddOp1(v, OP_IdxInsert, raw_data_reg + tuple_len); > - sqlite3VdbeChangeP4(v, -1, (char *)space, P4_SPACEPTR); > + if (op == OP_IdxInsert) { > + uint32_t tuple_raw_reg = raw_data_reg + space->def->field_count; > + sqlite3VdbeAddOp3(v, OP_MakeRecord, raw_data_reg, > + space->def->field_count, tuple_raw_reg); > + sqlite3VdbeAddOp1(v, op, tuple_raw_reg); > + sqlite3VdbeChangeP4(v, -1, (char *)space, P4_SPACEPTR); > + } else if (op == OP_IdxUpdate) { > + sqlite3VdbeAddOp4(v, op, raw_data_reg, raw_key_reg, > + upd_cols_reg, (char *)space, P4_SPACEPTR); > + } else { > + unreachable(); > + } I’d better introduce separate function (or simply inline) for emitting update operation. Lets keep vdbe_emit_insertion_completion as is. > sqlite3VdbeChangeP5(v, pik_flags); > } > > > void > @@ -4730,10 +4738,11 @@ void sqlite3WithPush(Parse *, With *, u8); > * @param reg_old Register with deleted row. > * @param reg_new Register with inserted row. > * @param changed_cols Array of updated columns. Can be NULL. > + * @param op operation, one of TK_UPDATE, TK_INSERT, TK_DELETE. > */ > void > fkey_emit_check(struct Parse *parser, struct Table *tab, int reg_old, > - int reg_new, const int *changed_cols); > + int reg_new, const int *changed_cols, int op); > > /** > * Emit VDBE code to do CASCADE, SET NULL or SET DEFAULT actions > diff --git a/src/box/sql/update.c b/src/box/sql/update.c > index 0e2d0fde8..aca8e4adc 100644 > --- a/src/box/sql/update.c > +++ b/src/box/sql/update.c > @@ -36,6 +36,7 @@ > #include "sqliteInt.h" > #include "box/session.h" > #include "tarantoolInt.h" > +#include "box/tuple_format.h" > #include "box/schema.h" > > void > @@ -112,6 +113,8 @@ sqlite3Update(Parse * pParse, /* The parser context */ > int regNew = 0; /* Content of the NEW.* table in triggers */ > int regOld = 0; /* Content of OLD.* table in triggers */ > int regKey = 0; /* composite PRIMARY KEY value */ > + /* Count of changed rows. Match aXRef items != -1. */ > + int upd_cols_cnt = 0; > > db = pParse->db; > if (pParse->nErr || db->mallocFailed) { > @@ -183,6 +186,7 @@ sqlite3Update(Parse * pParse, /* The parser context */ > goto update_cleanup; > } > aXRef[j] = i; > + upd_cols_cnt++; > break; > } > } > @@ -216,7 +220,7 @@ sqlite3Update(Parse * pParse, /* The parser context */ > regNewPk = ++pParse->nMem; > } > regNew = pParse->nMem + 1; > - pParse->nMem += def->field_count; > + pParse->nMem += def->field_count + 1; > > /* If we are trying to update a view, realize that view into > * an ephemeral table. > @@ -430,23 +434,64 @@ sqlite3Update(Parse * pParse, /* The parser context */ > vdbe_emit_constraint_checks(pParse, pTab, regNewPk + 1, > on_error, labelContinue, aXRef); > /* Do FK constraint checks. */ > - if (hasFK) > - fkey_emit_check(pParse, pTab, regOldPk, 0, aXRef); > - /* > - * Delete the index entries associated with the > - * current record. It can be already removed by > - * trigger or REPLACE conflict action. > - */ > - int addr1 = sqlite3VdbeAddOp4Int(v, OP_NotFound, pk_cursor, 0, > - regKey, nKey); > - assert(regNew == regNewPk + 1); > - sqlite3VdbeAddOp2(v, OP_Delete, pk_cursor, 0); > - sqlite3VdbeJumpHere(v, addr1); > - if (hasFK) > - fkey_emit_check(pParse, pTab, 0, regNewPk, aXRef); > - vdbe_emit_insertion_completion(v, space, regNew, > - pTab->def->field_count, > - on_error); > + if (hasFK) { > + fkey_emit_check(pParse, pTab, regOldPk, 0, aXRef, > + TK_UPDATE); > + } > + if (on_error == ON_CONFLICT_ACTION_REPLACE) { > + /* > + * Delete the index entries associated with the > + * current record. It can be already removed by > + * trigger or REPLACE conflict action. > + */ > + int not_found_lbl = > + sqlite3VdbeAddOp4Int(v, OP_NotFound, pk_cursor, > + 0, regKey, nKey); > + assert(regNew == regNewPk + 1); > + sqlite3VdbeAddOp2(v, OP_Delete, pk_cursor, 0); > + sqlite3VdbeJumpHere(v, not_found_lbl); > + } > + if (hasFK) { > + fkey_emit_check(pParse, pTab, 0, regNewPk, aXRef, > + TK_UPDATE); > + } > + if (on_error == ON_CONFLICT_ACTION_REPLACE) { > + vdbe_emit_insertion_completion(v, space, OP_IdxInsert, > + regNew, 0, 0, on_error); > + } else { > + int key_reg; > + if (okOnePass) { > + key_reg = sqlite3GetTempReg(pParse); > + const char *zAff = Nit: zAff -> aff_str. > + sql_space_index_affinity_str(pParse->db, > + def, > + pPk->def); > + sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, > + pk_part_count, key_reg, zAff, > + pk_part_count); > + } else { > + assert(nKey == 0); > + key_reg = regKey; > + } > + > + /* Prapare array of changed fields. */ Nit: prepare. > + uint32_t upd_cols_sz = upd_cols_cnt * sizeof(uint32_t); > + uint32_t *upd_cols = sqlite3DbMallocRaw(db, upd_cols_sz); > + if (upd_cols == NULL) > + goto update_cleanup; > + upd_cols_cnt = 0; > + for (uint32_t i = 0; i < def->field_count; i++) { > + if (aXRef[i] == -1) > + continue; > + upd_cols[upd_cols_cnt++] = i; > + } > + int upd_cols_reg = sqlite3GetTempReg(pParse); I remember that there was issue connected with different register allocation policy (during analyze registers were allocated with sqlite3GetTempReg and simple nMem++). Wouldn’t we face the same problem here? > + sqlite3VdbeAddOp4(v, OP_Blob, upd_cols_sz, upd_cols_reg, > + 0, (const char *)upd_cols, P4_DYNAMIC); > + vdbe_emit_insertion_completion(v, space, OP_IdxUpdate, > + regNew, key_reg, > + upd_cols_reg, on_error); > + } > /* > * Do any ON CASCADE, SET NULL or SET DEFAULT > * operations required to handle rows that refer > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c > index bf6f4cdd1..534d3e885 100644 > --- a/src/box/sql/vdbe.c > +++ b/src/box/sql/vdbe.c > > +/* Opcode: OP_IdxUpdate P1 P2 P3 P4 P5 > + * Synopsis: key=r[P1] Hmm, during vibe execution I see no ’synopsis’. What is more, I look at opcodes.h and: /* 114 */ "IdxUpdate" OpHelp("”), So, our script didn’t generate auxiliary information for opcode. Open an issue or investigate this case pls. If you fix it, extend synopsis with other args. > + * > + * Make space Update operation. Primary key fields could not be > + * modified - use OP_Replace opcode instead. > + * This opcode extends IdxInsert/IdxReplace interface to > + * transform to OP_Replace in particular case of sql operator > + * "UPDATE or REPLACE". > + * > + * @param P1 The first field of updated tuple. > + * @param P2 Index of a register with index key MakeRecord. > + * @param P3 Index of a register with upd_fields blob. > + * This blob has size sizeof(uint32_t)*upd_fields_cnt. > + * It's items are numbers of fields to be replaced with > + * new values from P1 blob. They must be sorted in > + * ascending order. I would slightly change comment: - * Make space Update operation. Primary key fields could not be - * modified - use OP_Replace opcode instead. - * This opcode extends IdxInsert/IdxReplace interface to - * transform to OP_Replace in particular case of sql operator - * "UPDATE or REPLACE". - * - * @param P1 The first field of updated tuple. - * @param P2 Index of a register with index key MakeRecord. + * Process UPDATE operation. Primary key fields can not be + * modified - in this case OP_IdxReplace is used instead. + * Under the hood it performs box_update() call. + * For the performance sake, it takes whole affected row (P1) + * and encodes into msgpack only fields to be updated (P3). + * + * @param P1 The first field to be updated. Fields are located + * in the range of [P1...] in decoded state. + * Encoded only fields which numbers are presented + * in @P3 array. + * @param P2 Encoded key to be passed to box_update(). * @param P3 Index of a register with upd_fields blob. - * This blob has size sizeof(uint32_t)*upd_fields_cnt. * It's items are numbers of fields to be replaced with - * new values from P1 blob. They must be sorted in - * ascending order. + * new values from P1. + * They must be sorted in ascending order. > + * @param P4 Pointer to the struct space to be updated. > + * @param P5 Flags. If P5 contains OPFLAG_NCHANGE, then VDBE > + * accounts the change in a case of successful > + * insertion in nChange counter. If P5 contains > + * OPFLAG_OE_IGNORE, then we are processing INSERT OR > + * INGORE statement. Thus, in case of conflict we don't > + * raise an error. > + */ > +case OP_IdxUpdate: { Don’t call it IdxUpdate - it has nothing in common with index. OP_IdxInsert is called this way due to historical reasons. Lets simply name it OP_Update. > + struct Mem *new_tuple = &aMem[pOp->p1]; > + if (pOp->p5 & OPFLAG_NCHANGE) > + p->nChange++; > + > + struct space *space = pOp->p4.space; > + assert(pOp->p4type == P4_SPACEPTR); > + > + struct Mem *key_mem = &aMem[pOp->p2]; > + assert((key_mem->flags & MEM_Blob) != 0); > + > + struct Mem *upd_fields_mem = &aMem[pOp->p3]; > + assert((upd_fields_mem->flags & MEM_Blob) != 0); > + uint32_t *upd_fields = (uint32_t *)upd_fields_mem->z; > + uint32_t upd_fields_cnt = upd_fields_mem->n / sizeof(uint32_t); > + > + /* Prepare Tarantool update ops msgpack. */ > + struct region *region = &fiber()->gc; > + size_t used = region_used(region); > + bool is_error = false; > + struct mpstream stream; > + mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb, > + mpstream_encode_error, &is_error); > + mpstream_encode_array(&stream, upd_fields_cnt); > + for (uint32_t i = 0; i < upd_fields_cnt; i++) { > + uint32_t field_idx = upd_fields[i]; > + assert(field_idx < space->def->field_count); > + mpstream_encode_array(&stream, 3); > + mpstream_encode_strn(&stream, "=", 1); > + mpstream_encode_uint(&stream, field_idx + 1); > + mpstream_encode_vdbe_mem(&stream, new_tuple + field_idx); > + } > + mpstream_flush(&stream); > + if (is_error) { > + diag_set(OutOfMemory, stream.pos - stream.buf, > + "mpstream_flush", "stream"); > + rc = SQL_TARANTOOL_ERROR; > + goto abort_due_to_error; > + } > + uint32_t ops_size = region_used(region) - used; > + const char *ops = region_join(region, ops_size); > + if (ops == NULL) { > + diag_set(OutOfMemory, ops_size, "region_join", "raw"); > + rc = SQL_TARANTOOL_ERROR; > + goto abort_due_to_error; > + } > + > + rc = SQLITE_OK; assert(rc == SQLITE_OK); If no error takes place, rc must be 0. Otherwise, error must be handled before. > + if (box_update(space->def->id, 0, key_mem->z, key_mem->z + key_mem->n, > + ops, ops + ops_size, TUPLE_INDEX_BASE, NULL) != 0) I see comment to struct request: /** Base field offset for UPDATE/UPSERT, e.g. 0 for C and 1 for Lua. */ int index_base; So why do you pass 1 instead of 0? > + rc = SQL_TARANTOOL_ERROR; > + > + if (pOp->p5 & OPFLAG_OE_IGNORE) { > + /* > + * Ignore any kind of failes and do not raise Nit: fails. > + * error message > + */ > + rc = SQLITE_OK; > + /* > + * If we are in trigger, increment ignore raised > + * counter. > + */ > + if (p->pFrame) > + p->ignoreRaised++; > + } else if (pOp->p5 & OPFLAG_OE_FAIL) { > + p->errorAction = ON_CONFLICT_ACTION_FAIL; > + } else if (pOp->p5 & OPFLAG_OE_ROLLBACK) { > + p->errorAction = ON_CONFLICT_ACTION_ROLLBACK; > + } > + if (rc != 0) > + goto abort_due_to_error; > + break; > +} > > /* Opcode: SInsert P1 P2 P3 * P5 > * Synopsis: space id = P1, key = r[P3], on error goto P2 > diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h > index 50bc35b2b..69898bbcf 100644 > --- a/src/box/sql/vdbeInt.h > +++ b/src/box/sql/vdbeInt.h > @@ -552,4 +552,10 @@ int sqlite3VdbeRecordCompareMsgpack(const void *key1, > struct UnpackedRecord *key2); > u32 sqlite3VdbeMsgpackGet(const unsigned char *buf, Mem * pMem); > > +struct mpstream; > + > +/** Perform encoding memory variable to stream. */ > +void > +mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var); > + > #endif /* !defined(SQLITE_VDBEINT_H) */ > diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c > index 22beba8be..1fee2673d 100644 > --- a/src/box/sql/vdbemem.c > +++ b/src/box/sql/vdbemem.c > @@ -40,6 +40,7 @@ > #include "vdbeInt.h" > #include "tarantoolInt.h" > #include "box/schema.h" > +#include "mpstream.h" > > #ifdef SQLITE_DEBUG > /* > @@ -1714,3 +1715,38 @@ sqlite3ValueBytes(sqlite3_value * pVal) > return 0; > return valueBytes(pVal); > } > + > +void > +mpstream_encode_vdbe_mem(struct mpstream *stream, struct Mem *var) Can we use this routine to encode MsgPack during execution of OP_MakeRecord? Now it uses sqlite3VdbeMsgpackRecordPut() which is almost the same. > diff --git a/src/mpstream.c b/src/mpstream.c > index e4f7950ba..843fc959e 100644 > --- a/src/mpstream.c > +++ b/src/mpstream.c > @@ -175,3 +175,23 @@ mpstream_encode_bool(struct mpstream *stream, bool val) > char *pos = mp_encode_bool(data, val); > mpstream_advance(stream, pos - data); > } > + > +void > +mpstream_encode_raw(struct mpstream *stream, const char *raw, uint32_t len) Hm, it doesn’t encode in fact. Mb better call _copy_raw? > +{ > + char *data = mpstream_reserve(stream, len); > + if (data == NULL) > + return; > + memcpy(data, raw, len); > + mpstream_advance(stream, len); > +} > > #if defined(__cplusplus) > } /* extern "C" */ > #endif /* defined(__cplusplus) */ At this point I didn’t review test changes.
next prev parent reply other threads:[~2018-12-25 17:26 UTC|newest] Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top 2018-12-19 15:37 [tarantool-patches] [PATCH v1 0/3] " Kirill Shcherbatov 2018-12-19 15:37 ` [tarantool-patches] [PATCH v1 1/3] sql: clean-up vdbe_emit_constraint_checks Kirill Shcherbatov 2018-12-25 17:26 ` [tarantool-patches] " n.pettik 2018-12-19 15:37 ` [tarantool-patches] [PATCH v1 2/3] sql: fix sql_vdbe_mem_alloc_region result memory Kirill Shcherbatov 2018-12-25 17:26 ` [tarantool-patches] " n.pettik 2018-12-19 15:37 ` [tarantool-patches] [PATCH v1 3/3] sql: do not use OP_Delete+OP_Insert for UPDATES Kirill Shcherbatov 2018-12-25 17:26 ` n.pettik [this message] 2018-12-26 8:35 ` [tarantool-patches] " Kirill Shcherbatov 2018-12-28 14:17 ` n.pettik 2018-12-20 20:41 ` [tarantool-patches] Re: [PATCH v1 0/3] " Vladislav Shpilevoy
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=487E532B-E70C-4707-890A-3D68ADAE0929@tarantool.org \ --to=korablev@tarantool.org \ --cc=kshcherbatov@tarantool.org \ --cc=tarantool-patches@freelists.org \ --subject='[tarantool-patches] Re: [PATCH v1 3/3] sql: do not use OP_Delete+OP_Insert for UPDATES' \ /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