From: Imeev Mergen <imeevma@tarantool.org> To: "n.pettik" <korablev@tarantool.org>, tarantool-patches@freelists.org Cc: Vladislav Shpilevoy <v.shpilevoy@tarantool.org> Subject: [tarantool-patches] Re: [PATCH v8 2/3] sql: return all generated ids via IPROTO Date: Fri, 2 Nov 2018 21:52:10 +0300 [thread overview] Message-ID: <81dc397f-aa49-15b2-5416-1f11931a11e5@tarantool.org> (raw) In-Reply-To: <6167E418-CCCC-4596-9E6C-F04C02045456@tarantool.org> [-- Attachment #1: Type: text/plain, Size: 37350 bytes --] Hi! Thank you for review! My answer, diff between patches and new patch below. Changes were made only in commit named by "sql: return all generated ids via IPROTO". After changes were made and diff saved I rebased this branch to current 2.1. After this new patch description were formed, so it is a bit different from old patch with applied diff. On 10/30/18 5:30 PM, n.pettik wrote: >> On 29 Oct 2018, at 20:33,imeevma@tarantool.org wrote: >> >> According to documentation some JDBC functions have an ability to >> return all ids that were generated in executed INSERT statement. >> This patch gives a way to implement such functionality. > I guess you should be more specific in describing such things. > I don’t ask you to dive into details, but explaining main idea of > implementation would definitely help reviewer to do his/her job. A bit extended this message. Not sure if it is enough. >> Closes #2618 >> — >> diff --git a/src/box/execute.c b/src/box/execute.c >> index 24459b4..ebf3962 100644 >> --- a/src/box/execute.c >> +++ b/src/box/execute.c >> @@ -42,6 +42,7 @@ >> #include "schema.h" >> #include "port.h" >> #include "tuple.h" >> +#include "sql/vdbe.h" >> >> >> diff --git a/src/box/execute.h b/src/box/execute.h >> index f21393b..614d3d0 100644 >> --- a/src/box/execute.h >> +++ b/src/box/execute.h >> @@ -42,6 +42,7 @@ extern "C" { >> /** Keys of IPROTO_SQL_INFO map. */ >> enum sql_info_key { >> SQL_INFO_ROW_COUNT = 0, >> + SQL_INFO_GENERATED_IDS = 1, > I vote for _AUTOINCREMENT_IDS, see comments below. > Anyway, in JDBC it is called GENERATED_KEYS. So, we either > follow JDBC convention or use our naming. In the latter case, > AUTOINCREMENT_IDS sounds more solid. Renamed to SQL_INFO_AUTOINCREMENT_IDS. >> sql_info_key_MAX, >> }; >> >> /** >> + * Append a new sequence value to Vdbe. List of automatically >> + * generated identifiers is returned as metadata of SQL response. >> + * >> + * @param Vdbe VDBE to save id in. >> + * @param id ID to save in VDBE. >> + * @retval 0 Success. >> + * @retval -1 Error. > It looks terrible. Does this function really need comment at all? Removed. >> + */ >> +int >> +vdbe_add_new_generated_id(struct Vdbe *vdbe, int64_t id); >> >> + >> +int >> +vdbe_add_new_generated_id(struct Vdbe *vdbe, int64_t id) >> +{ >> + assert(vdbe != NULL); >> + struct id_entry *id_entry = region_alloc(&fiber()->gc, sizeof(*id_entry)); >> + if (id_entry == NULL) { >> + diag_set(OutOfMemory, sizeof(*id_entry), "region_alloc", "id_entry”); > Out of 80. > >> + return -1; >> + } >> + id_entry->id = id; >> + stailq_add_tail_entry(vdbe_generated_ids(vdbe), id_entry, link); >> + return 0; >> +} >> + >> /* >> * Execute as much of a VDBE program as we can. >> * This is the core of sqlite3_step(). >> @@ -2995,8 +3015,9 @@ case OP_TransactionBegin: { >> * If there is no active transaction, raise an error. >> */ >> case OP_TransactionCommit: { >> - if (box_txn()) { >> - if (box_txn_commit() != 0) { >> + struct txn *txn = in_txn(); >> + if (txn != NULL) { >> + if (txn_commit(txn) != 0) { > From first sigh it doesn’t look like diff related to patch. > Add comment pointing out that txn_commit() doesn’t > invoke fiber_gc() anymore and VDBE takes care of it. Extended comment of this opcode. >> /** >> * Prepare given VDBE to execution: initialize structs connected >> @@ -197,6 +198,15 @@ sql_alloc_txn(); >> int >> sql_vdbe_prepare(struct Vdbe *vdbe); >> >> +/** >> + * Return pointer to list of generated ids. >> + * >> + * @param vdbe VDBE to get list of generated ids from. >> + * @retval List of generated ids. >> + */ >> +struct stailq * >> +vdbe_generated_ids(struct Vdbe *vdbe); > As for me, “generated_ids” seems to be too general name… > I would call it “autoincrement_ids" or sort of. Renamed to autoinc_id_list here. >> + >> int sqlite3VdbeAddOp0(Vdbe *, int); >> int sqlite3VdbeAddOp1(Vdbe *, int, int); >> int sqlite3VdbeAddOp2(Vdbe *, int, int, int); >> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h >> index ce97f49..7a7d3de 100644 >> --- a/src/box/sql/vdbeInt.h >> +++ b/src/box/sql/vdbeInt.h >> @@ -367,6 +367,11 @@ struct Vdbe { >> struct sql_txn *psql_txn; >> /** The auto-commit flag. */ >> bool auto_commit; >> + /** >> + * List of ids generated in current VDBE. It is returned >> + * as metadata of SQL response. >> + */ >> + struct stailq generated_ids; > Again: “autoincrement_ids”. Term “generated_ids” can be confused > with entity ids. For example, during execution of <CREATE TABLE> > statement VDBE operates on space id, index id etc (IMHO). Renamed to autoinc_id_list here. >> /* When allocating a new Vdbe object, all of the fields below should be >> * initialized to zero or NULL >> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c >> index 6e42d05..ae73f25 100644 >> --- a/src/box/sql/vdbeaux.c >> +++ b/src/box/sql/vdbeaux.c >> @@ -57,6 +57,7 @@ sqlite3VdbeCreate(Parse * pParse) >> return 0; >> memset(p, 0, sizeof(Vdbe)); >> p->db = db; >> + stailq_create(&p->generated_ids); >> if (db->pVdbe) { >> db->pVdbe->pPrev = p; >> } >> >> @@ -2414,9 +2417,9 @@ sqlite3VdbeHalt(Vdbe * p) >> * key constraints to hold up the transaction. This means a commit >> * is required. >> */ >> - rc = box_txn_commit() == >> - 0 ? SQLITE_OK : >> - SQL_TARANTOOL_ERROR; >> + rc = (in_txn() == NULL || >> + txn_commit(in_txn()) == 0) ? >> + SQLITE_OK : SQL_TARANTOOL_ERROR; >> closeCursorsAndFree(p); >> } >> if (rc == SQLITE_BUSY && !p->pDelFrame) { >> @@ -2780,6 +2783,8 @@ sqlite3VdbeDelete(Vdbe * p) >> } >> p->magic = VDBE_MAGIC_DEAD; >> p->db = 0; >> + if (in_txn() == NULL) >> + fiber_gc(); > Describe this change (in code, obviously): > “VDBE is responsible for releasing region …" > and so on. Added comment. >> sqlite3DbFree(db, p); >> } >> >> diff --git a/src/box/txn.h b/src/box/txn.h >> index e74c14d..87b55da 100644 >> --- a/src/box/txn.h >> +++ b/src/box/txn.h >> @@ -49,6 +49,7 @@ struct engine; >> struct space; >> struct tuple; >> struct xrow_header; >> +struct Vdbe; >> >> enum { >> /** >> @@ -117,6 +118,19 @@ struct sql_txn { >> * VDBE to the next in the same transaction. >> */ >> uint32_t fk_deferred_count; >> + /** Current VDBE. */ >> + struct Vdbe *vdbe; >> +}; >> + >> +/** >> + * An element of list of autogenerated ids, being returned as SQL >> + * response metadata. >> + */ >> +struct id_entry { > “id_entry” is too common name, be more specific pls. Renamed to struct autoinc_id_entry. Still, name of variable is 'id_entry'. >> + /** A link in a generated id list. */ >> + struct stailq_entry link; >> + /** Generated id. */ >> + int64_t id; > These comments are too obvious, they only contaminate > source code. Removed these comments. >> }; >> >> /** >> * FFI bindings: do not throw exceptions, do not accept extra >> diff --git a/test/sql/iproto.result b/test/sql/iproto.result >> index af474bc..af4fc12 100644 >> --- a/test/sql/iproto.result >> +++ b/test/sql/iproto.result >> @@ -580,6 +580,98 @@ cn:close() >> box.sql.execute('drop table test') >> --- >> ... >> +-- gh-2618 Return generated columns after INSERT in IPROTO. >> +-- Return all ids generated in current INSERT statement. > Add pls case where generated ids are less than 0. Added. *Diff between versions:* diff --git a/src/box/execute.c b/src/box/execute.c index ebf3962..64c0be6 100644 --- a/src/box/execute.c +++ b/src/box/execute.c @@ -638,24 +638,24 @@ err: } else { keys = 1; assert(port_tuple->size == 0); - struct stailq *generated_ids = - vdbe_generated_ids((struct Vdbe *)stmt); - uint32_t map_size = stailq_empty(generated_ids) ? 1 : 2; + struct stailq *autoinc_id_list = + vdbe_autoinc_id_list((struct Vdbe *)stmt); + uint32_t map_size = stailq_empty(autoinc_id_list) ? 1 : 2; if (iproto_reply_map_key(out, map_size, IPROTO_SQL_INFO) != 0) goto err; uint64_t id_count = 0; int changes = sqlite3_changes(db); int size = mp_sizeof_uint(SQL_INFO_ROW_COUNT) + mp_sizeof_uint(changes); - if (!stailq_empty(generated_ids)) { - struct id_entry *id_entry; - stailq_foreach_entry(id_entry, generated_ids, link) { + if (!stailq_empty(autoinc_id_list)) { + struct autoinc_id_entry *id_entry; + stailq_foreach_entry(id_entry, autoinc_id_list, link) { size += id_entry->id >= 0 ? mp_sizeof_uint(id_entry->id) : mp_sizeof_int(id_entry->id); id_count++; } - size += mp_sizeof_uint(SQL_INFO_GENERATED_IDS) + + size += mp_sizeof_uint(SQL_INFO_AUTOINCREMENT_IDS) + mp_sizeof_array(id_count); } char *buf = obuf_alloc(out, size); @@ -665,11 +665,11 @@ err: } buf = mp_encode_uint(buf, SQL_INFO_ROW_COUNT); buf = mp_encode_uint(buf, changes); - if (!stailq_empty(generated_ids)) { - buf = mp_encode_uint(buf, SQL_INFO_GENERATED_IDS); + if (!stailq_empty(autoinc_id_list)) { + buf = mp_encode_uint(buf, SQL_INFO_AUTOINCREMENT_IDS); buf = mp_encode_array(buf, id_count); - struct id_entry *id_entry; - stailq_foreach_entry(id_entry, generated_ids, link) { + struct autoinc_id_entry *id_entry; + stailq_foreach_entry(id_entry, autoinc_id_list, link) { buf = id_entry->id >= 0 ? mp_encode_uint(buf, id_entry->id) : mp_encode_int(buf, id_entry->id); diff --git a/src/box/execute.h b/src/box/execute.h index 614d3d0..77bfd79 100644 --- a/src/box/execute.h +++ b/src/box/execute.h @@ -42,7 +42,7 @@ extern "C" { /** Keys of IPROTO_SQL_INFO map. */ enum sql_info_key { SQL_INFO_ROW_COUNT = 0, - SQL_INFO_GENERATED_IDS = 1, + SQL_INFO_AUTOINCREMENT_IDS = 1, sql_info_key_MAX, }; diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index 5a137e0..836ed6e 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -680,11 +680,11 @@ netbox_decode_sql_info(struct lua_State *L, const char **data) lua_setfield(L, -2, "rowcount"); /* * If data have two elements then second is - * SQL_INFO_GENERATED_IDS. + * SQL_INFO_AUTOINCREMENT_IDS. */ if (map_size == 2) { key = mp_decode_uint(data); - assert(key == SQL_INFO_GENERATED_IDS); + assert(key == SQL_INFO_AUTOINCREMENT_IDS); (void)key; uint64_t count = mp_decode_array(data); assert(count > 0); @@ -695,7 +695,7 @@ netbox_decode_sql_info(struct lua_State *L, const char **data) luaL_pushint64(L, id); lua_rawseti(L, -2, j + 1); } - lua_setfield(L, -2, "generated_ids"); + lua_setfield(L, -2, "autoincrement_ids"); } } diff --git a/src/box/sequence.c b/src/box/sequence.c index 9a87a56..c9828c0 100644 --- a/src/box/sequence.c +++ b/src/box/sequence.c @@ -196,7 +196,7 @@ sequence_next(struct sequence *seq, int64_t *result) return -1; *result = def->start; if (txn_vdbe() != NULL && - vdbe_add_new_generated_id(txn_vdbe(), *result) != 0) + vdbe_add_new_autoinc_id(txn_vdbe(), *result) != 0) return -1; return 0; } @@ -233,7 +233,7 @@ done: unreachable(); *result = value; if (txn_vdbe() != NULL && - vdbe_add_new_generated_id(txn_vdbe(), value) != 0) + vdbe_add_new_autoinc_id(txn_vdbe(), value) != 0) return -1; return 0; overflow: diff --git a/src/box/sequence.h b/src/box/sequence.h index 7180ead..4419427 100644 --- a/src/box/sequence.h +++ b/src/box/sequence.h @@ -140,17 +140,8 @@ sequence_next(struct sequence *seq, int64_t *result); int access_check_sequence(struct sequence *seq); -/** - * Append a new sequence value to Vdbe. List of automatically - * generated identifiers is returned as metadata of SQL response. - * - * @param Vdbe VDBE to save id in. - * @param id ID to save in VDBE. - * @retval 0 Success. - * @retval -1 Error. - */ int -vdbe_add_new_generated_id(struct Vdbe *vdbe, int64_t id); +vdbe_add_new_autoinc_id(struct Vdbe *vdbe, int64_t id); /** * Create an iterator over sequence data. diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index 1e732a9..dd763e7 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -579,22 +579,24 @@ out2Prerelease(Vdbe *p, VdbeOp *pOp) } struct stailq * -vdbe_generated_ids(struct Vdbe *vdbe) +vdbe_autoinc_id_list(struct Vdbe *vdbe) { - return &vdbe->generated_ids; + return &vdbe->autoinc_id_list; } int -vdbe_add_new_generated_id(struct Vdbe *vdbe, int64_t id) +vdbe_add_new_autoinc_id(struct Vdbe *vdbe, int64_t id) { assert(vdbe != NULL); - struct id_entry *id_entry = region_alloc(&fiber()->gc, sizeof(*id_entry)); + struct autoinc_id_entry *id_entry = region_alloc(&fiber()->gc, + sizeof(*id_entry)); if (id_entry == NULL) { - diag_set(OutOfMemory, sizeof(*id_entry), "region_alloc", "id_entry"); + diag_set(OutOfMemory, sizeof(*id_entry), "region_alloc", + "id_entry"); return -1; } id_entry->id = id; - stailq_add_tail_entry(vdbe_generated_ids(vdbe), id_entry, link); + stailq_add_tail_entry(vdbe_autoinc_id_list(vdbe), id_entry, link); return 0; } @@ -3014,6 +3016,9 @@ case OP_TransactionBegin: { * * Commit Tarantool's transaction. * If there is no active transaction, raise an error. + * After txn was commited VDBE should take care of region as + * txn_commit() doesn't invoke fiber_gc(). Region is needed to + * get information of autogenerated ids during sql response dump. */ case OP_TransactionCommit: { struct txn *txn = in_txn(); diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h index ed7fb2f..9546d42 100644 --- a/src/box/sql/vdbe.h +++ b/src/box/sql/vdbe.h @@ -205,7 +205,7 @@ sql_vdbe_prepare(struct Vdbe *vdbe); * @retval List of generated ids. */ struct stailq * -vdbe_generated_ids(struct Vdbe *vdbe); +vdbe_autoinc_id_list(struct Vdbe *vdbe); int sqlite3VdbeAddOp0(Vdbe *, int); int sqlite3VdbeAddOp1(Vdbe *, int, int); diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h index 19b35b7..d16a51e 100644 --- a/src/box/sql/vdbeInt.h +++ b/src/box/sql/vdbeInt.h @@ -369,7 +369,7 @@ struct Vdbe { * List of ids generated in current VDBE. It is returned * as metadata of SQL response. */ - struct stailq generated_ids; + struct stailq autoinc_id_list; /* When allocating a new Vdbe object, all of the fields below should be * initialized to zero or NULL diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c index 3efe252..78cf2c4 100644 --- a/src/box/sql/vdbeaux.c +++ b/src/box/sql/vdbeaux.c @@ -57,7 +57,7 @@ sqlite3VdbeCreate(Parse * pParse) return 0; memset(p, 0, sizeof(Vdbe)); p->db = db; - stailq_create(&p->generated_ids); + stailq_create(&p->autoinc_id_list); if (db->pVdbe) { db->pVdbe->pPrev = p; } @@ -2779,6 +2779,10 @@ sqlite3VdbeDelete(Vdbe * p) } p->magic = VDBE_MAGIC_DEAD; p->db = 0; + /* + * VDBE is responsible for releasing region after txn + * was commited. + */ if (in_txn() == NULL) fiber_gc(); sqlite3DbFree(db, p); diff --git a/src/box/txn.h b/src/box/txn.h index 87b55da..de5cb0d 100644 --- a/src/box/txn.h +++ b/src/box/txn.h @@ -126,10 +126,8 @@ struct sql_txn { * An element of list of autogenerated ids, being returned as SQL * response metadata. */ -struct id_entry { - /** A link in a generated id list. */ +struct autoinc_id_entry { struct stailq_entry link; - /** Generated id. */ int64_t id; }; diff --git a/test/sql/iproto.result b/test/sql/iproto.result index af4fc12..06cb4b9 100644 --- a/test/sql/iproto.result +++ b/test/sql/iproto.result @@ -37,6 +37,9 @@ box.sql.execute('select * from test') box.schema.user.grant('guest','read,write,execute', 'universe') --- ... +box.schema.user.grant('guest', 'create', 'space') +--- +... cn = remote.connect(box.cfg.listen) --- ... @@ -594,7 +597,7 @@ cn:execute('insert into test values (1, 1)') ... cn:execute('insert into test values (null, 2)') --- -- generated_ids: +- autoincrement_ids: - 2 rowcount: 1 ... @@ -604,14 +607,14 @@ cn:execute('update test set a = 11 where id == 1') ... cn:execute('insert into test values (100, 1), (null, 1), (120, 1), (null, 1)') --- -- generated_ids: +- autoincrement_ids: - 101 - 121 rowcount: 4 ... cn:execute('insert into test values (null, 1), (null, 1), (null, 1), (null, 1), (null, 1)') --- -- generated_ids: +- autoincrement_ids: - 122 - 123 - 124 @@ -654,12 +657,27 @@ _ = box.space.TEST:on_replace(push_id) ... cn:execute('insert into test values (null, 1)') --- -- generated_ids: +- autoincrement_ids: - 127 - 1 - 2 rowcount: 1 ... +box.sql.execute('create table test3 (id int primary key autoincrement)') +--- +... +box.schema.sequence.alter('TEST3', {min=-10000, step=-10}) +--- +... +cn:execute('insert into TEST3 values (null), (null), (null), (null)') +--- +- autoincrement_ids: + - 1 + - -9 + - -19 + - -29 + rowcount: 4 +... cn:close() --- ... @@ -672,9 +690,15 @@ s:drop() sq:drop() --- ... +box.sql.execute('drop table test3') +--- +... box.schema.user.revoke('guest', 'read,write,execute', 'universe') --- ... +box.schema.user.revoke('guest', 'create', 'space') +--- +... space = nil --- ... diff --git a/test/sql/iproto.test.lua b/test/sql/iproto.test.lua index a2caa57..51ac43b 100644 --- a/test/sql/iproto.test.lua +++ b/test/sql/iproto.test.lua @@ -10,6 +10,7 @@ space:replace{4, 5, '6'} space:replace{7, 8.5, '9'} box.sql.execute('select * from test') box.schema.user.grant('guest','read,write,execute', 'universe') +box.schema.user.grant('guest', 'create', 'space') cn = remote.connect(box.cfg.listen) cn:ping() @@ -220,12 +221,18 @@ function push_id() s:replace{box.NULL} s:replace{box.NULL} end _ = box.space.TEST:on_replace(push_id) cn:execute('insert into test values (null, 1)') +box.sql.execute('create table test3 (id int primary key autoincrement)') +box.schema.sequence.alter('TEST3', {min=-10000, step=-10}) +cn:execute('insert into TEST3 values (null), (null), (null), (null)') + cn:close() box.sql.execute('drop table test') s:drop() sq:drop() +box.sql.execute('drop table test3') box.schema.user.revoke('guest', 'read,write,execute', 'universe') +box.schema.user.revoke('guest', 'create', 'space') space = nil -- Cleanup xlog *New version:* commit 58f70a2cc6bfbed679f1601b09ba1a5d9db05872 Author: Mergen Imeev <imeevma@gmail.com> Date: Wed Sep 26 22:07:54 2018 +0300 sql: return all generated ids via IPROTO According to documentation some JDBC functions have an ability to return all ids that were generated in executed INSERT statement. This patch gives a way to implement such functionality. After this patch all ids autogenerated during VDBE execution will be saved and returned through IPROTO. Closes #2618 diff --git a/src/box/execute.c b/src/box/execute.c index 24459b4..64c0be6 100644 --- a/src/box/execute.c +++ b/src/box/execute.c @@ -42,6 +42,7 @@ #include "schema.h" #include "port.h" #include "tuple.h" +#include "sql/vdbe.h" const char *sql_type_strs[] = { NULL, @@ -637,11 +638,26 @@ err: } else { keys = 1; assert(port_tuple->size == 0); - if (iproto_reply_map_key(out, 1, IPROTO_SQL_INFO) != 0) + struct stailq *autoinc_id_list = + vdbe_autoinc_id_list((struct Vdbe *)stmt); + uint32_t map_size = stailq_empty(autoinc_id_list) ? 1 : 2; + if (iproto_reply_map_key(out, map_size, IPROTO_SQL_INFO) != 0) goto err; + uint64_t id_count = 0; int changes = sqlite3_changes(db); int size = mp_sizeof_uint(SQL_INFO_ROW_COUNT) + mp_sizeof_uint(changes); + if (!stailq_empty(autoinc_id_list)) { + struct autoinc_id_entry *id_entry; + stailq_foreach_entry(id_entry, autoinc_id_list, link) { + size += id_entry->id >= 0 ? + mp_sizeof_uint(id_entry->id) : + mp_sizeof_int(id_entry->id); + id_count++; + } + size += mp_sizeof_uint(SQL_INFO_AUTOINCREMENT_IDS) + + mp_sizeof_array(id_count); + } char *buf = obuf_alloc(out, size); if (buf == NULL) { diag_set(OutOfMemory, size, "obuf_alloc", "buf"); @@ -649,6 +665,16 @@ err: } buf = mp_encode_uint(buf, SQL_INFO_ROW_COUNT); buf = mp_encode_uint(buf, changes); + if (!stailq_empty(autoinc_id_list)) { + buf = mp_encode_uint(buf, SQL_INFO_AUTOINCREMENT_IDS); + buf = mp_encode_array(buf, id_count); + struct autoinc_id_entry *id_entry; + stailq_foreach_entry(id_entry, autoinc_id_list, link) { + buf = id_entry->id >= 0 ? + mp_encode_uint(buf, id_entry->id) : + mp_encode_int(buf, id_entry->id); + } + } } iproto_reply_sql(out, &header_svp, response->sync, schema_version, keys); diff --git a/src/box/execute.h b/src/box/execute.h index f21393b..77bfd79 100644 --- a/src/box/execute.h +++ b/src/box/execute.h @@ -42,6 +42,7 @@ extern "C" { /** Keys of IPROTO_SQL_INFO map. */ enum sql_info_key { SQL_INFO_ROW_COUNT = 0, + SQL_INFO_AUTOINCREMENT_IDS = 1, sql_info_key_MAX, }; diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c index a928a4c..836ed6e 100644 --- a/src/box/lua/net_box.c +++ b/src/box/lua/net_box.c @@ -668,16 +668,35 @@ static void netbox_decode_sql_info(struct lua_State *L, const char **data) { uint32_t map_size = mp_decode_map(data); - /* Only SQL_INFO_ROW_COUNT is available. */ - assert(map_size == 1); - (void) map_size; + assert(map_size == 1 || map_size == 2); + lua_newtable(L); + /* + * First element in data is SQL_INFO_ROW_COUNT. + */ uint32_t key = mp_decode_uint(data); assert(key == SQL_INFO_ROW_COUNT); - (void) key; uint32_t row_count = mp_decode_uint(data); - lua_createtable(L, 0, 1); lua_pushinteger(L, row_count); lua_setfield(L, -2, "rowcount"); + /* + * If data have two elements then second is + * SQL_INFO_AUTOINCREMENT_IDS. + */ + if (map_size == 2) { + key = mp_decode_uint(data); + assert(key == SQL_INFO_AUTOINCREMENT_IDS); + (void)key; + uint64_t count = mp_decode_array(data); + assert(count > 0); + lua_createtable(L, 0, count); + for (uint32_t j = 0; j < count; ++j) { + int64_t id; + mp_read_int64(data, &id); + luaL_pushint64(L, id); + lua_rawseti(L, -2, j + 1); + } + lua_setfield(L, -2, "autoincrement_ids"); + } } static int diff --git a/src/box/sequence.c b/src/box/sequence.c index 35b7605..c9828c0 100644 --- a/src/box/sequence.c +++ b/src/box/sequence.c @@ -38,6 +38,7 @@ #include <small/mempool.h> #include <msgpuck/msgpuck.h> +#include "txn.h" #include "diag.h" #include "error.h" #include "errcode.h" @@ -194,6 +195,9 @@ sequence_next(struct sequence *seq, int64_t *result) new_data) == light_sequence_end) return -1; *result = def->start; + if (txn_vdbe() != NULL && + vdbe_add_new_autoinc_id(txn_vdbe(), *result) != 0) + return -1; return 0; } old_data = light_sequence_get(&sequence_data_index, pos); @@ -228,6 +232,9 @@ done: new_data, &old_data) == light_sequence_end) unreachable(); *result = value; + if (txn_vdbe() != NULL && + vdbe_add_new_autoinc_id(txn_vdbe(), value) != 0) + return -1; return 0; overflow: if (!def->cycle) { diff --git a/src/box/sequence.h b/src/box/sequence.h index 0f6c8da..4419427 100644 --- a/src/box/sequence.h +++ b/src/box/sequence.h @@ -43,6 +43,7 @@ extern "C" { #endif /* defined(__cplusplus) */ struct iterator; +struct Vdbe; /** Sequence metadata. */ struct sequence_def { @@ -139,6 +140,9 @@ sequence_next(struct sequence *seq, int64_t *result); int access_check_sequence(struct sequence *seq); +int +vdbe_add_new_autoinc_id(struct Vdbe *vdbe, int64_t id); + /** * Create an iterator over sequence data. * diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index d6d6b7e..78fd28a 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -578,6 +578,28 @@ out2Prerelease(Vdbe *p, VdbeOp *pOp) } } +struct stailq * +vdbe_autoinc_id_list(struct Vdbe *vdbe) +{ + return &vdbe->autoinc_id_list; +} + +int +vdbe_add_new_autoinc_id(struct Vdbe *vdbe, int64_t id) +{ + assert(vdbe != NULL); + struct autoinc_id_entry *id_entry = region_alloc(&fiber()->gc, + sizeof(*id_entry)); + if (id_entry == NULL) { + diag_set(OutOfMemory, sizeof(*id_entry), "region_alloc", + "id_entry"); + return -1; + } + id_entry->id = id; + stailq_add_tail_entry(vdbe_autoinc_id_list(vdbe), id_entry, link); + return 0; +} + /* * Execute as much of a VDBE program as we can. * This is the core of sqlite3_step(). @@ -2997,10 +3019,14 @@ case OP_TransactionBegin: { * * Commit Tarantool's transaction. * If there is no active transaction, raise an error. + * After txn was commited VDBE should take care of region as + * txn_commit() doesn't invoke fiber_gc(). Region is needed to + * get information of autogenerated ids during sql response dump. */ case OP_TransactionCommit: { - if (box_txn()) { - if (box_txn_commit() != 0) { + struct txn *txn = in_txn(); + if (txn != NULL) { + if (txn_commit(txn) != 0) { rc = SQL_TARANTOOL_ERROR; goto abort_due_to_error; } @@ -3044,7 +3070,7 @@ case OP_TransactionRollback: { */ case OP_TTransaction: { if (!box_txn()) { - if (box_txn_begin() != 0) { + if (sql_txn_begin(p) != 0) { rc = SQL_TARANTOOL_ERROR; goto abort_due_to_error; } diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h index d5da5d5..9546d42 100644 --- a/src/box/sql/vdbe.h +++ b/src/box/sql/vdbe.h @@ -179,10 +179,11 @@ Vdbe *sqlite3VdbeCreate(Parse *); * Allocate and initialize SQL-specific struct which completes * original Tarantool's txn struct using region allocator. * + * @param v Vdbe with which associate this transaction. * @retval NULL on OOM, new psql_txn struct on success. **/ struct sql_txn * -sql_alloc_txn(); +sql_alloc_txn(struct Vdbe *v); /** * Prepare given VDBE to execution: initialize structs connected @@ -197,6 +198,15 @@ sql_alloc_txn(); int sql_vdbe_prepare(struct Vdbe *vdbe); +/** + * Return pointer to list of generated ids. + * + * @param vdbe VDBE to get list of generated ids from. + * @retval List of generated ids. + */ +struct stailq * +vdbe_autoinc_id_list(struct Vdbe *vdbe); + int sqlite3VdbeAddOp0(Vdbe *, int); int sqlite3VdbeAddOp1(Vdbe *, int, int); int sqlite3VdbeAddOp2(Vdbe *, int, int, int); diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h index ce97f49..cfb327a 100644 --- a/src/box/sql/vdbeInt.h +++ b/src/box/sql/vdbeInt.h @@ -367,6 +367,11 @@ struct Vdbe { struct sql_txn *psql_txn; /** The auto-commit flag. */ bool auto_commit; + /** + * List of ids generated in current VDBE. It is returned + * as metadata of SQL response. + */ + struct stailq autoinc_id_list; /* When allocating a new Vdbe object, all of the fields below should be * initialized to zero or NULL diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c index 6e42d05..1e72a4d 100644 --- a/src/box/sql/vdbeaux.c +++ b/src/box/sql/vdbeaux.c @@ -57,6 +57,7 @@ sqlite3VdbeCreate(Parse * pParse) return 0; memset(p, 0, sizeof(Vdbe)); p->db = db; + stailq_create(&p->autoinc_id_list); if (db->pVdbe) { db->pVdbe->pPrev = p; } @@ -75,7 +76,7 @@ sqlite3VdbeCreate(Parse * pParse) } struct sql_txn * -sql_alloc_txn() +sql_alloc_txn(struct Vdbe *v) { struct sql_txn *txn = region_alloc_object(&fiber()->gc, struct sql_txn); @@ -84,6 +85,8 @@ sql_alloc_txn() "struct sql_txn"); return NULL; } + txn->vdbe = v; + v->psql_txn = txn; txn->pSavepoint = NULL; txn->fk_deferred_count = 0; return txn; @@ -103,11 +106,12 @@ sql_vdbe_prepare(struct Vdbe *vdbe) * check FK violations, at least now. */ if (txn->psql_txn == NULL) { - txn->psql_txn = sql_alloc_txn(); + txn->psql_txn = sql_alloc_txn(vdbe); if (txn->psql_txn == NULL) return -1; + } else { + vdbe->psql_txn = txn->psql_txn; } - vdbe->psql_txn = txn->psql_txn; } else { vdbe->psql_txn = NULL; } @@ -2261,12 +2265,11 @@ sql_txn_begin(Vdbe *p) struct txn *ptxn = txn_begin(false); if (ptxn == NULL) return -1; - ptxn->psql_txn = sql_alloc_txn(); + ptxn->psql_txn = sql_alloc_txn(p); if (ptxn->psql_txn == NULL) { box_txn_rollback(); return -1; } - p->psql_txn = ptxn->psql_txn; return 0; } @@ -2414,9 +2417,9 @@ sqlite3VdbeHalt(Vdbe * p) * key constraints to hold up the transaction. This means a commit * is required. */ - rc = box_txn_commit() == - 0 ? SQLITE_OK : - SQL_TARANTOOL_ERROR; + rc = (in_txn() == NULL || + txn_commit(in_txn()) == 0) ? + SQLITE_OK : SQL_TARANTOOL_ERROR; closeCursorsAndFree(p); } if (rc == SQLITE_BUSY && !p->pDelFrame) { @@ -2780,6 +2783,12 @@ sqlite3VdbeDelete(Vdbe * p) } p->magic = VDBE_MAGIC_DEAD; p->db = 0; + /* + * VDBE is responsible for releasing region after txn + * was commited. + */ + if (in_txn() == NULL) + fiber_gc(); sqlite3DbFree(db, p); } diff --git a/src/box/txn.h b/src/box/txn.h index e74c14d..de5cb0d 100644 --- a/src/box/txn.h +++ b/src/box/txn.h @@ -49,6 +49,7 @@ struct engine; struct space; struct tuple; struct xrow_header; +struct Vdbe; enum { /** @@ -117,6 +118,17 @@ struct sql_txn { * VDBE to the next in the same transaction. */ uint32_t fk_deferred_count; + /** Current VDBE. */ + struct Vdbe *vdbe; +}; + +/** + * An element of list of autogenerated ids, being returned as SQL + * response metadata. + */ +struct autoinc_id_entry { + struct stailq_entry link; + int64_t id; }; struct txn { @@ -337,6 +349,20 @@ txn_last_stmt(struct txn *txn) void txn_on_stop(struct trigger *trigger, void *event); +/** + * Return VDBE that is being currently executed. + * + * @retval VDBE that is being currently executed. + * @retval NULL Either txn or ptxn_sql or vdbe is NULL; + */ +static inline struct Vdbe * +txn_vdbe() +{ + struct txn *txn = in_txn(); + if (txn == NULL || txn->psql_txn == NULL) + return NULL; + return txn->psql_txn->vdbe; +} /** * FFI bindings: do not throw exceptions, do not accept extra diff --git a/test/sql/iproto.result b/test/sql/iproto.result index ee34f6f..06cb4b9 100644 --- a/test/sql/iproto.result +++ b/test/sql/iproto.result @@ -583,6 +583,116 @@ cn:close() box.sql.execute('drop table test') --- ... +-- gh-2618 Return generated columns after INSERT in IPROTO. +-- Return all ids generated in current INSERT statement. +box.sql.execute('create table test (id integer primary key autoincrement, a integer)') +--- +... +cn = remote.connect(box.cfg.listen) +--- +... +cn:execute('insert into test values (1, 1)') +--- +- rowcount: 1 +... +cn:execute('insert into test values (null, 2)') +--- +- autoincrement_ids: + - 2 + rowcount: 1 +... +cn:execute('update test set a = 11 where id == 1') +--- +- rowcount: 1 +... +cn:execute('insert into test values (100, 1), (null, 1), (120, 1), (null, 1)') +--- +- autoincrement_ids: + - 101 + - 121 + rowcount: 4 +... +cn:execute('insert into test values (null, 1), (null, 1), (null, 1), (null, 1), (null, 1)') +--- +- autoincrement_ids: + - 122 + - 123 + - 124 + - 125 + - 126 + rowcount: 5 +... +cn:execute('select * from test') +--- +- metadata: + - name: ID + - name: A + rows: + - [1, 11] + - [2, 2] + - [100, 1] + - [101, 1] + - [120, 1] + - [121, 1] + - [122, 1] + - [123, 1] + - [124, 1] + - [125, 1] + - [126, 1] +... +s = box.schema.create_space('test2', {engine = engine}) +--- +... +sq = box.schema.sequence.create('test2') +--- +... +pk = s:create_index('pk', {sequence = 'test2'}) +--- +... +function push_id() s:replace{box.NULL} s:replace{box.NULL} end +--- +... +_ = box.space.TEST:on_replace(push_id) +--- +... +cn:execute('insert into test values (null, 1)') +--- +- autoincrement_ids: + - 127 + - 1 + - 2 + rowcount: 1 +... +box.sql.execute('create table test3 (id int primary key autoincrement)') +--- +... +box.schema.sequence.alter('TEST3', {min=-10000, step=-10}) +--- +... +cn:execute('insert into TEST3 values (null), (null), (null), (null)') +--- +- autoincrement_ids: + - 1 + - -9 + - -19 + - -29 + rowcount: 4 +... +cn:close() +--- +... +box.sql.execute('drop table test') +--- +... +s:drop() +--- +... +sq:drop() +--- +... +box.sql.execute('drop table test3') +--- +... box.schema.user.revoke('guest', 'read,write,execute', 'universe') --- ... diff --git a/test/sql/iproto.test.lua b/test/sql/iproto.test.lua index b065590..51ac43b 100644 --- a/test/sql/iproto.test.lua +++ b/test/sql/iproto.test.lua @@ -202,6 +202,35 @@ future4:wait_result() cn:close() box.sql.execute('drop table test') +-- gh-2618 Return generated columns after INSERT in IPROTO. +-- Return all ids generated in current INSERT statement. +box.sql.execute('create table test (id integer primary key autoincrement, a integer)') + +cn = remote.connect(box.cfg.listen) +cn:execute('insert into test values (1, 1)') +cn:execute('insert into test values (null, 2)') +cn:execute('update test set a = 11 where id == 1') +cn:execute('insert into test values (100, 1), (null, 1), (120, 1), (null, 1)') +cn:execute('insert into test values (null, 1), (null, 1), (null, 1), (null, 1), (null, 1)') +cn:execute('select * from test') + +s = box.schema.create_space('test2', {engine = engine}) +sq = box.schema.sequence.create('test2') +pk = s:create_index('pk', {sequence = 'test2'}) +function push_id() s:replace{box.NULL} s:replace{box.NULL} end +_ = box.space.TEST:on_replace(push_id) +cn:execute('insert into test values (null, 1)') + +box.sql.execute('create table test3 (id int primary key autoincrement)') +box.schema.sequence.alter('TEST3', {min=-10000, step=-10}) +cn:execute('insert into TEST3 values (null), (null), (null), (null)') + +cn:close() +box.sql.execute('drop table test') +s:drop() +sq:drop() +box.sql.execute('drop table test3') + box.schema.user.revoke('guest', 'read,write,execute', 'universe') box.schema.user.revoke('guest', 'create', 'space') space = nil [-- Attachment #2: Type: text/html, Size: 50444 bytes --]
next prev parent reply other threads:[~2018-11-02 18:52 UTC|newest] Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top 2018-10-29 17:33 [tarantool-patches] [PATCH v8 0/3] " imeevma 2018-10-29 17:33 ` [tarantool-patches] [PATCH v8 1/3] box: factor fiber_gc out of txn_commit imeevma 2018-10-30 14:30 ` [tarantool-patches] " n.pettik 2018-10-30 19:15 ` Vladislav Shpilevoy 2018-10-30 20:03 ` Vladislav Shpilevoy 2018-10-30 20:06 ` Vladislav Shpilevoy 2018-10-30 21:32 ` Vladislav Shpilevoy 2018-10-30 23:08 ` n.pettik 2018-10-31 9:18 ` Vladislav Shpilevoy 2018-10-31 9:30 ` n.pettik 2018-10-29 17:33 ` [tarantool-patches] [PATCH v8 2/3] sql: return all generated ids via IPROTO imeevma 2018-10-30 14:30 ` [tarantool-patches] " n.pettik 2018-11-02 18:52 ` Imeev Mergen [this message] 2018-11-09 7:51 ` n.pettik 2018-10-29 17:33 ` [tarantool-patches] [PATCH v8 3/3] sql: remove psql_txn from Vdbe imeevma 2018-10-30 14:30 ` [tarantool-patches] " n.pettik 2018-10-30 19:41 ` Vladislav Shpilevoy 2018-11-09 8:02 ` [tarantool-patches] Re: [PATCH v8 0/3] sql: return all generated ids via IPROTO Kirill Yukhin
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=81dc397f-aa49-15b2-5416-1f11931a11e5@tarantool.org \ --to=imeevma@tarantool.org \ --cc=korablev@tarantool.org \ --cc=tarantool-patches@freelists.org \ --cc=v.shpilevoy@tarantool.org \ --subject='[tarantool-patches] Re: [PATCH v8 2/3] sql: return all generated ids via IPROTO' \ /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