Skip to content

Commit

Permalink
sql: return last_insert_id via IPROTO
Browse files Browse the repository at this point in the history
After this patch client will get last_insert_id
as additional metadata when he executes some
queries.

Part of ...

... document
Title: SQL function last_insert_id.
Function last_insert_id returns first autogenerated ID in
last INSERT/REPLACE statement in current session. Return
value of function is undetermined when more than one
INSERT/REPLACE statements executed asynchronously.
Example:
CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, a INTEGER);
INSERT INTO test VALUES (NULL, 1);
SELECT last_insert_id();

Temporary commit.

TODO:
1) Check that PK != NULL and delete unnesessary 'if' if so.
2) Add tests into gh-2981: insert ... (null, null, null).
3) Review fix.
4) Revert secuence back in case of quety failture.
5) Find out purpose of deleted 'if' in vdbe.c.
  • Loading branch information
ImeevMA committed Aug 10, 2018
1 parent 29df7d3 commit a414c5d
Show file tree
Hide file tree
Showing 12 changed files with 258 additions and 43 deletions.
11 changes: 10 additions & 1 deletion src/box/execute.c
Expand Up @@ -42,6 +42,7 @@
#include "schema.h"
#include "port.h"
#include "tuple.h"
#include "session.h"

const char *sql_type_strs[] = {
NULL,
Expand Down Expand Up @@ -640,15 +641,23 @@ sql_response_dump(struct sql_response *response, struct obuf *out)
if (iproto_reply_map_key(out, 1, IPROTO_SQL_INFO) != 0)
goto err;
int changes = sqlite3_changes(db);
int64_t last_insert_id = current_session()->sql_last_insert_id;
int size = mp_sizeof_uint(SQL_INFO_ROW_COUNT) +
mp_sizeof_uint(changes);
mp_sizeof_uint(changes) +
mp_sizeof_uint(SQL_INFO_LAST_INSERT_ID) +
(last_insert_id >= 0 ?
mp_sizeof_uint(last_insert_id) :
mp_sizeof_int(last_insert_id));
char *buf = obuf_alloc(out, size);
if (buf == NULL) {
diag_set(OutOfMemory, size, "obuf_alloc", "buf");
goto err;
}
buf = mp_encode_uint(buf, SQL_INFO_ROW_COUNT);
buf = mp_encode_uint(buf, changes);
buf = mp_encode_uint(buf, SQL_INFO_LAST_INSERT_ID);
buf = last_insert_id < 0 ? mp_encode_int(buf, last_insert_id) :
mp_encode_uint(buf, last_insert_id);
}
iproto_reply_sql(out, &header_svp, response->sync, schema_version,
keys);
Expand Down
1 change: 1 addition & 0 deletions src/box/execute.h
Expand Up @@ -42,6 +42,7 @@ extern "C" {
/** Keys of IPROTO_SQL_INFO map. */
enum sql_info_key {
SQL_INFO_ROW_COUNT = 0,
SQL_INFO_LAST_INSERT_ID = 1,
sql_info_key_MAX,
};

Expand Down
12 changes: 9 additions & 3 deletions src/box/lua/net_box.c
Expand Up @@ -667,16 +667,22 @@ 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;
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);
key = mp_decode_uint(data);
assert(key == SQL_INFO_LAST_INSERT_ID);
(void) key;
int64_t last_insert_id = mp_typeof(**data) == MP_UINT ?
(int64_t) mp_decode_uint(data) :
mp_decode_int(data);
lua_createtable(L, 0, 2);
lua_pushinteger(L, row_count);
lua_setfield(L, -2, "rowcount");
lua_pushinteger(L, last_insert_id);
lua_setfield(L, -2, "last_insert_id");
}

static int
Expand Down
1 change: 1 addition & 0 deletions src/box/session.cc
Expand Up @@ -108,6 +108,7 @@ session_create(enum session_type type)
session->type = type;
session->sql_flags = default_flags;
session->sql_default_engine = SQL_STORAGE_ENGINE_MEMTX;
session->sql_last_insert_id = 0;

/* For on_connect triggers. */
credentials_init(&session->credentials, guest_user->auth_token,
Expand Down
2 changes: 2 additions & 0 deletions src/box/session.h
Expand Up @@ -92,6 +92,8 @@ union session_meta {
struct session {
/** Session id. */
uint64_t id;
/** First autogenerated ID in last INSERT/REPLACE query */
int64_t sql_last_insert_id;
/** SQL Tarantool Default storage engine. */
uint8_t sql_default_engine;
/** SQL Connection flag for current user session */
Expand Down
18 changes: 18 additions & 0 deletions src/box/sql/func.c
Expand Up @@ -38,6 +38,7 @@
#include "vdbeInt.h"
#include "version.h"
#include "coll.h"
#include "src/box/session.h"
#include <unicode/ustring.h>
#include <unicode/ucasemap.h>
#include <unicode/ucnv.h>
Expand Down Expand Up @@ -600,6 +601,22 @@ changes(sqlite3_context * context, int NotUsed, sqlite3_value ** NotUsed2)
sqlite3_result_int(context, sqlite3_changes(db));
}

/*
* Implementation of the last_insert_id() function. Returns first
* autogenerated ID in last INSERT/REPLACE in current session.
*
* @param context Context being used.
* @param not_used Unused.
* @param not_used2 Unused.
*/
static void
last_insert_id(sqlite3_context *context, int not_used,
sqlite3_value **not_used2)
{
UNUSED_PARAMETER2(not_used, not_used2);
sqlite3_result_int(context, current_session()->sql_last_insert_id);
}

/*
* Implementation of the total_changes() SQL function. The return value is
* the same as the sqlite3_total_changes() API function.
Expand Down Expand Up @@ -1847,6 +1864,7 @@ sqlite3RegisterBuiltinFunctions(void)
FUNCTION(lower, 1, 0, 1, LowerICUFunc),
FUNCTION(hex, 1, 0, 0, hexFunc),
FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQLITE_FUNC_COALESCE),
VFUNCTION(last_insert_id, 0, 0, 0, last_insert_id),
VFUNCTION(random, 0, 0, 0, randomFunc),
VFUNCTION(randomblob, 1, 0, 0, randomBlob),
FUNCTION(nullif, 2, 0, 1, nullifFunc),
Expand Down
9 changes: 5 additions & 4 deletions src/box/sql/insert.c
Expand Up @@ -739,9 +739,7 @@ sqlite3Insert(Parse * pParse, /* Parser context */
if (j < 0 || nColumn == 0
|| (pColumn && j >= pColumn->nId)) {
if (i == pTab->iAutoIncPKey) {
sqlite3VdbeAddOp2(v,
OP_NextAutoincValue,
pTab->def->id,
sqlite3VdbeAddOp2(v, OP_Null, 0,
iRegStore);
continue;
}
Expand Down Expand Up @@ -824,7 +822,10 @@ sqlite3Insert(Parse * pParse, /* Parser context */
iRegStore);
}
}

if (pTab->iAutoIncPKey >= 0) {
sqlite3VdbeAddOp2(v, OP_NextAutoincValue, pTab->def->id,
regData + pTab->iAutoIncPKey);
}
/* Generate code to check constraints and generate index keys
and do the insertion.
*/
Expand Down
18 changes: 17 additions & 1 deletion src/box/sql/vdbe.c
Expand Up @@ -599,6 +599,11 @@ int sqlite3VdbeExec(Vdbe *p)
u64 start; /* CPU clock count at start of opcode */
#endif
struct session *user_session = current_session();
/*
* Field sql_last_insert_id of current session should be
* set no more than once for each query.
*/
bool last_insert_id_is_set = false;
/*** INSERT STACK UNION HERE ***/

assert(p->magic==VDBE_MAGIC_RUN); /* sqlite3_step() verifies this */
Expand Down Expand Up @@ -1146,6 +1151,10 @@ case OP_NextAutoincValue: {
assert(pOp->p1 > 0);
assert(pOp->p2 > 0);

pOut = &p->aMem[pOp->p2];
if ((pOut->flags & MEM_Null) == 0)
break;

struct space *space = space_by_id(pOp->p1);
if (space == NULL) {
rc = SQL_TARANTOOL_ERROR;
Expand All @@ -1163,6 +1172,13 @@ case OP_NextAutoincValue: {
pOut->flags = MEM_Int;
pOut->u.i = value;

/* set last_insert_id of session */
if (!last_insert_id_is_set) {
struct session *session = current_session();
session->sql_last_insert_id = value;
last_insert_id_is_set = true;
}

break;
}

Expand Down Expand Up @@ -3773,7 +3789,7 @@ case OP_FCopy: { /* out2 */

if ((pOp->p3 & OPFLAG_NOOP_IF_NULL) && (pIn1->flags & MEM_Null)) {
pOut = &aMem[pOp->p2];
if (pOut->flags & MEM_Undefined) pOut->flags = MEM_Null;
pOut->flags = MEM_Null;
/* Flag is set and register is NULL -> do nothing */
} else {
assert(memIsValid(pIn1));
Expand Down
27 changes: 26 additions & 1 deletion test/sql-tap/insert3.test.lua
@@ -1,6 +1,6 @@
#!/usr/bin/env tarantool
test = require("sqltester")
test:plan(18)
test:plan(20)

--!./tcltestrunner.lua
-- 2005 January 13
Expand Down Expand Up @@ -262,6 +262,31 @@ test:do_execsql_test(
-- <insert3-4.1>
})

-- gh-2618 function last_insert_id
test:execsql("CREATE TABLE t8(id INTEGER PRIMARY KEY AUTOINCREMENT, a INT);")
test:do_execsql_test(
"insert3-5.1",
[[
INSERT INTO t8 VALUES (null, 11);
SELECT LAST_INSERT_ID();
]], {
-- <insert3-5.1>
1
-- <insert3-5.1>
})

test:do_execsql_test(
"insert3-5.2",
[[
INSERT INTO t8 VALUES (null, 44), (null, 55), (null, 66);
SELECT LAST_INSERT_ID();
]], {
-- <insert3-5.1>
2
-- <insert3-5.1>
})


test:drop_all_tables()
---------------------------------------------------------------------------
-- While developing tests for a different feature (savepoint) the following
Expand Down
3 changes: 2 additions & 1 deletion test/sql/errinj.result
Expand Up @@ -73,7 +73,8 @@ while f1:status() ~= 'dead' do fiber.sleep(0) end
...
insert_res
---
- rowcount: 1
- last_insert_id: 0
rowcount: 1
...
select_res
---
Expand Down

0 comments on commit a414c5d

Please sign in to comment.