From c741896d4200f06b477cb1d71ef4c30c15f3c914 Mon Sep 17 00:00:00 2001 From: Nikita Pettik <korablev@tarantool.org> Date: Fri, 8 Dec 2017 13:42:03 +0300 Subject: [PATCH] sql: introduce Tarantool's ephemeral tables - Added intermediate functions between SQL and Tarantool, tarantoolSqlite3Ephemeral* in order to create and use Tarantool's ephemeral tables. Also, for this reason added BTCF_TEphemeral flag to BtCursor instead of BTCF_TaCursor to call appropriate functions. - Added struct space* to SQL cursor, which allows to delete ephemeral tables. (since ephemeral tables don't have space_id and it is impossible to find them by traditional space_id lookup). Moreover, it enables us to avoid using box API inasmuch as we can directly call space_execute_* functions having struct space*. - As the first stage of implemenation, ephemeral tables were successfully replaced for DELETE FROM ... WHERE statement. It utilized ephemeral space as intermediate holder where all tuples to be removed are placed before actual deletion. Part of #2680 --- src/box/index.cc | 3 + src/box/sql.c | 244 ++++++++++++++++++++++++++++++++++++- src/box/sql/btree.c | 33 ++++- src/box/sql/btree.h | 1 + src/box/sql/btreeInt.h | 1 + src/box/sql/delete.c | 2 +- src/box/sql/opcodes.c | 103 ++++++++-------- src/box/sql/opcodes.h | 117 +++++++++--------- src/box/sql/tarantoolInt.h | 7 ++ src/box/sql/vdbe.c | 49 ++++++++ 10 files changed, 445 insertions(+), 115 deletions(-) diff --git a/src/box/index.cc b/src/box/index.cc index 3c644f0a8e..dfddeb32be 100644 --- a/src/box/index.cc +++ b/src/box/index.cc @@ -449,6 +449,9 @@ int iterator_next(struct iterator *it, struct tuple **ret) { assert(it->next != NULL); + /* In case of ephemeral space there is no need to check schema version */ + if (it->space_id == 0) + return it->next(it, ret); if (unlikely(it->schema_version != schema_version)) { struct space *space = space_by_id(it->space_id); if (space == NULL) diff --git a/src/box/sql.c b/src/box/sql.c index d4b343b4ab..53a5676487 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -48,12 +48,15 @@ #include "schema.h" #include "box.h" #include "txn.h" +#include "space.h" #include "space_def.h" #include "index_def.h" #include "tuple.h" #include "fiber.h" #include "small/region.h" #include "session.h" +#include "xrow.h" +#include "iproto_constants.h" static sqlite3 *db; @@ -160,6 +163,8 @@ struct ta_cursor { box_iterator_t *iter; struct tuple *tuple_last; enum iterator_type type; + /* Used only by ephemeral spaces, for ordinary space == NULL. */ + struct space *ephem_space; char key[1]; }; @@ -173,6 +178,13 @@ cursor_seek(BtCursor *pCur, int *pRes, enum iterator_type type, static int cursor_advance(BtCursor *pCur, int *pRes); +static int +cursor_ephemeral_seek(BtCursor *pCur, int *pRes, enum iterator_type type, + const char *key, const char *key_end); + +static int +cursor_ephemeral_advance(BtCursor *pCur, int *pRes); + const char *tarantoolErrorMessage() { return box_error_message(box_error_last()); @@ -180,7 +192,8 @@ const char *tarantoolErrorMessage() int tarantoolSqlite3CloseCursor(BtCursor *pCur) { - assert(pCur->curFlags & BTCF_TaCursor); + assert(pCur->curFlags & BTCF_TaCursor || + pCur->curFlags & BTCF_TEphemCursor); struct ta_cursor *c = pCur->pTaCursor; @@ -196,7 +209,8 @@ int tarantoolSqlite3CloseCursor(BtCursor *pCur) const void *tarantoolSqlite3PayloadFetch(BtCursor *pCur, u32 *pAmt) { - assert(pCur->curFlags & BTCF_TaCursor); + assert(pCur->curFlags & BTCF_TaCursor || + pCur->curFlags & BTCF_TEphemCursor); struct ta_cursor *c = pCur->pTaCursor; @@ -226,6 +240,16 @@ tarantoolSqlite3TupleColumnFast(BtCursor *pCur, u32 fieldno, u32 *field_size) return field; } +/* + * Set cursor to the first tuple in ephemeral space. + * It is a simple wrapper around cursor_ephemeral_seek. + */ +int tarantoolSqlite3EphemeralFirst(BtCursor *pCur, int *pRes) +{ + return cursor_ephemeral_seek(pCur, pRes, ITER_GE, + nil_key, nil_key + sizeof(nil_key)); +} + int tarantoolSqlite3First(BtCursor *pCur, int *pRes) { return cursor_seek(pCur, pRes, ITER_GE, @@ -238,6 +262,24 @@ int tarantoolSqlite3Last(BtCursor *pCur, int *pRes) nil_key, nil_key + sizeof(nil_key)); } +/* + * Set cursor to the next entry in ephemeral space. + * If state of cursor is invalid (e.g. it is still under construction, + * or already destroyed), it immediately returns. + */ +int tarantoolSqlite3EphemeralNext(BtCursor *pCur, int *pRes) +{ + assert(pCur->curFlags & BTCF_TEphemCursor); + if (pCur->eState == CURSOR_INVALID) { + *pRes = 1; + return SQLITE_OK; + } + assert(pCur->pTaCursor); + assert(iterator_direction( + ((struct ta_cursor *)pCur->pTaCursor)->type) > 0); + return cursor_ephemeral_advance(pCur, pRes); +} + int tarantoolSqlite3Next(BtCursor *pCur, int *pRes) { assert(pCur->curFlags & BTCF_TaCursor); @@ -346,6 +388,103 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry) return SQLITE_OK; } +/* + * Create ephemeral space and set cursor to the first entry. Features of + * ephemeral spaces: id == 0, name == "ephemeral", memtx engine (in future it + * can be changed, but now only memtx engine is supported), primary index + * which covers all fields and no secondary indexes. All fields are scalar + * and nullable. + * + * @param pCur Cursor which will point to the new ephemeral space. + * @param field_count Number of fields in ephemeral space. + * + * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise. + */ +int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count) +{ + assert(pCur); + assert(pCur->curFlags & BTCF_TEphemCursor); + + struct space_def *ephemer_space_def = + space_def_new(0 /* space id */, 0 /* user id */, field_count, + "ephemeral", strlen("ephemeral"), + "memtx", strlen("memtx"), + &space_opts_default, &field_def_default, + 0 /* length of field_def */); + + struct key_def *ephemer_key_def = key_def_new(field_count); + assert(ephemer_key_def); + for (uint32_t part = 0; part < field_count; ++part) { + key_def_set_part(ephemer_key_def, part /* part no */, + part /* filed no */, + FIELD_TYPE_SCALAR, true /* is_nullable */, + NULL /* coll */); + } + + struct index_def *ephemer_index_def = + index_def_new(0 /*space id */, 0 /* index id */, "ephemer_idx", + strlen("ephemer_idx"), TREE, &index_opts_default, + ephemer_key_def, NULL /* pk def */); + + struct rlist key_list; + rlist_create(&key_list); + rlist_add_entry(&key_list, ephemer_index_def, link); + + struct space *ephemer_new_space = space_new_ephemeral(ephemer_space_def, + &key_list); + struct ta_cursor *c = NULL; + c = cursor_create(c, field_count /* key size */); + if (!c) + return SQLITE_NOMEM; + + c->ephem_space = ephemer_new_space; + pCur->pTaCursor = c; + + int unused; + return tarantoolSqlite3EphemeralFirst(pCur, &unused); +} + +/* + * Insert tuple which is contained in pX into ephemeral space. In contrast to + * ordinary spaces, there is no need to create and fill request or handle + * transaction routine. + * + * @param pCur Cursor pointing to ephemeral space. + * @param pX Payload containing tuple to insert. + * + * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise. + */ +int tarantoolSqlite3EphemeralInsert(BtCursor *pCur, const BtreePayload *pX) +{ + assert(pCur); + assert(pCur->curFlags & BTCF_TEphemCursor); + struct ta_cursor *c = pCur->pTaCursor; + assert(c); + assert(c->ephem_space); + mp_tuple_assert(pX->pKey, pX->pKey + pX->nKey); + + struct space *space = c->ephem_space; + if (space_ephemeral_replace(space, pX->pKey, + pX->pKey + pX->nKey) != 0) { + diag_log(); + return SQLITE_TARANTOOL_ERROR; + } + return SQLITE_OK; +} + +/* Simply delete ephemeral space calling space_delete(). */ +int tarantoolSqlite3EphemeralDrop(BtCursor *pCur) +{ + assert(pCur); + assert(pCur->curFlags & BTCF_TEphemCursor); + + struct ta_cursor *c = pCur->pTaCursor; + assert(c->ephem_space); + space_delete(c->ephem_space); + + return SQLITE_OK; +} + int tarantoolSqlite3Insert(BtCursor *pCur, const BtreePayload *pX) { assert(pCur->curFlags & BTCF_TaCursor); @@ -968,13 +1107,16 @@ cursor_create(struct ta_cursor *c, size_t key_size) { size_t size; struct ta_cursor *res; + struct space *ephem_space; if (c) { size = c->size; + ephem_space = c->ephem_space; if (size - offsetof(struct ta_cursor, key) >= key_size) return c; } else { size = sizeof(*c); + ephem_space = NULL; } while (size - offsetof(struct ta_cursor, key) < key_size) @@ -983,6 +1125,7 @@ cursor_create(struct ta_cursor *c, size_t key_size) res = realloc(c, size); if (res) { res->size = size; + res->ephem_space = ephem_space; if (!c) { res->iter = NULL; res->tuple_last = NULL; @@ -991,6 +1134,68 @@ cursor_create(struct ta_cursor *c, size_t key_size) return res; } +/* + * Create new Tarantool iterator and set it to the first entry found by + * given key. If cursor already contains iterator, it will be freed. + * + * @param pCur Cursor which points to ephemeral space. + * @param pRes Flag which is == 0 if reached end of space, == 1 otherwise. + * @param type Type of iterator. + * @param key Start of buffer containing key. + * @param key_end End of key. + * + * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise. + */ +static int +cursor_ephemeral_seek(BtCursor *pCur, int *pRes, enum iterator_type type, + const char *key, const char *key_end) +{ + assert(pCur->curFlags & BTCF_TEphemCursor); + assert(key != NULL && key_end != NULL); + assert(type >= 0 && type < iterator_type_MAX); + mp_tuple_assert(key, key_end); + + struct ta_cursor *c = pCur->pTaCursor; + assert(c->ephem_space); + + size_t key_size = 0; + if (c && c->iter) { + box_iterator_free(c->iter); + c->iter = NULL; + } + + if (type == ITER_EQ || type == ITER_REQ) { + key_size = (size_t)(key_end - key); + } + c = cursor_create(c, key_size); + if (!c) { + *pRes = 1; + return SQLITE_NOMEM; + } + pCur->pTaCursor = c; + + uint32_t part_count = mp_decode_array(&key); + struct space *ephem_space = c->ephem_space; + struct index *index = *ephem_space->index; + if (key_validate(index->def, type, key, part_count)) { + diag_log(); + return SQLITE_TARANTOOL_ERROR; + } + + struct iterator *it = index_create_iterator(*ephem_space->index, type, + key, part_count); + if (it == NULL) { + pCur->eState = CURSOR_INVALID; + return SQLITE_TARANTOOL_ERROR; + } + c->iter = it; + c->type = type; + pCur->eState = CURSOR_VALID; + pCur->curIntKey = 0; + + return cursor_ephemeral_advance(pCur, pRes); +} + /* Cursor positioning. */ static int cursor_seek(BtCursor *pCur, int *pRes, enum iterator_type type, @@ -1038,6 +1243,41 @@ cursor_seek(BtCursor *pCur, int *pRes, enum iterator_type type, return cursor_advance(pCur, pRes); } +/* + * Move cursor to the next entry in ephemeral space. + * New tuple is refed and saved in cursord. + * Tuple from previous call is unrefed. + * + * @param pCur Cursor which will point to the new ephemeral space. + * @param pRes Flag which is == 0 if reached end of space, == 1 otherwise. + * + * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise. + */ +static int +cursor_ephemeral_advance(BtCursor *pCur, int *pRes) +{ + assert(pCur->curFlags & BTCF_TEphemCursor); + struct ta_cursor *c = pCur->pTaCursor; + assert(c); + assert(c->iter); + + struct tuple *tuple; + if (iterator_next(c->iter, &tuple) != 0) + return SQLITE_TARANTOOL_ERROR; + if (tuple != NULL && tuple_bless(tuple) == NULL) + return SQLITE_TARANTOOL_ERROR; + if (c->tuple_last) box_tuple_unref(c->tuple_last); + if (tuple) { + box_tuple_ref(tuple); + *pRes = 0; + } else { + pCur->eState = CURSOR_INVALID; + *pRes = 1; + } + c->tuple_last = tuple; + return SQLITE_OK; +} + static int cursor_advance(BtCursor *pCur, int *pRes) { diff --git a/src/box/sql/btree.c b/src/box/sql/btree.c index ca14ae6cd8..af735a3a2e 100644 --- a/src/box/sql/btree.c +++ b/src/box/sql/btree.c @@ -2967,6 +2967,17 @@ sqlite3BtreeCursor(Btree * p, /* The btree */ return rc; } +int +sqlite3BtreeCursorEphemeral(Btree* p, int iTable, int wrFlag, + struct KeyInfo *pKeyInfo, BtCursor * pCur) +{ + btreeCursor(p, iTable, wrFlag, pKeyInfo, pCur); + pCur->curFlags |= BTCF_TEphemCursor; + pCur->pTaCursor = 0; + + return SQLITE_OK; +} + /* * Return the size of a BtCursor object in bytes. * @@ -3027,6 +3038,9 @@ sqlite3BtreeCloseCursor(BtCursor * pCur) sqlite3_free(pCur->aOverflow); if (pCur->curFlags & BTCF_TaCursor) { tarantoolSqlite3CloseCursor(pCur); + } else if (pCur->curFlags & BTCF_TEphemCursor) { + tarantoolSqlite3EphemeralDrop(pCur); + tarantoolSqlite3CloseCursor(pCur); } /* sqlite3_free(pCur); */ } @@ -3118,7 +3132,8 @@ sqlite3BtreePayloadSize(BtCursor * pCur) { assert(cursorHoldsMutex(pCur)); assert(pCur->eState == CURSOR_VALID); - if (pCur->curFlags & BTCF_TaCursor) { + if (pCur->curFlags & BTCF_TaCursor || + pCur->curFlags & BTCF_TEphemCursor) { u32 sz; tarantoolSqlite3PayloadFetch(pCur, &sz); return sz; @@ -3246,7 +3261,8 @@ accessPayload(BtCursor * pCur, /* Cursor pointing to entry to read from */ int eOp /* zero to read. non-zero to write. */ ) { - if (pCur->curFlags & BTCF_TaCursor) { + if (pCur->curFlags & BTCF_TaCursor || + pCur->curFlags & BTCF_TEphemCursor) { const void *pPayload; u32 sz; pPayload = tarantoolSqlite3PayloadFetch(pCur, &sz); @@ -3488,8 +3504,10 @@ sqlite3BtreePayload(BtCursor * pCur, u32 offset, u32 amt, void *pBuf) assert(cursorHoldsMutex(pCur)); assert(pCur->eState == CURSOR_VALID); assert((pCur->curFlags & BTCF_TaCursor) || + (pCur->curFlags & BTCF_TEphemCursor) || (pCur->iPage >= 0 && pCur->apPage[pCur->iPage])); assert((pCur->curFlags & BTCF_TaCursor) || + (pCur->curFlags & BTCF_TEphemCursor) || pCur->aiIdx[pCur->iPage] < pCur->apPage[pCur->iPage]->nCell); return accessPayload(pCur, offset, amt, (unsigned char *)pBuf, 0); } @@ -3834,6 +3852,9 @@ sqlite3BtreeFirst(BtCursor * pCur, int *pRes) if (pCur->curFlags & BTCF_TaCursor) { return tarantoolSqlite3First(pCur, pRes); } + if (pCur->curFlags & BTCF_TEphemCursor) { + return tarantoolSqlite3EphemeralFirst(pCur, pRes); + } rc = moveToRoot(pCur); if (rc == SQLITE_OK) { if (pCur->eState == CURSOR_INVALID) { @@ -4319,13 +4340,16 @@ sqlite3BtreeNext(BtCursor * pCur, int *pRes) pCur->info.nSize = 0; pCur->curFlags &= ~(BTCF_ValidNKey | BTCF_ValidOvfl); *pRes = 0; - if (pCur->curFlags & BTCF_TaCursor) { + if (pCur->curFlags & BTCF_TaCursor || + pCur->curFlags & BTCF_TEphemCursor) { if (pCur->eState != CURSOR_VALID) { int rc = restoreCursorPosition(pCur); if (rc != SQLITE_OK) { return rc; } } + if (pCur->curFlags & BTCF_TEphemCursor) + return tarantoolSqlite3EphemeralNext(pCur, pRes); return tarantoolSqlite3Next(pCur, pRes); } if (pCur->eState != CURSOR_VALID) @@ -6797,6 +6821,9 @@ sqlite3BtreeInsert(BtCursor * pCur, /* Insert data into the table of this cursor return tarantoolSqlite3Insert(pCur, pX); } + if (pCur->curFlags & BTCF_TEphemCursor) { + return tarantoolSqlite3EphemeralInsert(pCur, pX); + } /* Save the positions of any other cursors open on this table. * * In some cases, the call to btreeMoveto() below is a no-op. For diff --git a/src/box/sql/btree.h b/src/box/sql/btree.h index 2b5b687cdb..df0f24f069 100644 --- a/src/box/sql/btree.h +++ b/src/box/sql/btree.h @@ -221,6 +221,7 @@ int sqlite3BtreeCursor(Btree *, /* BTree containing table to open */ struct KeyInfo *, /* First argument to compare function */ BtCursor * pCursor /* Space to write cursor structure */ ); +int sqlite3BtreeCursorEphemeral(Btree *, int, int, struct KeyInfo *, BtCursor *); int sqlite3BtreeCursorSize(void); void sqlite3BtreeCursorZero(BtCursor *); void sqlite3BtreeCursorHintFlags(BtCursor *, unsigned); diff --git a/src/box/sql/btreeInt.h b/src/box/sql/btreeInt.h index fc4a6ca66e..4e6331c3b9 100644 --- a/src/box/sql/btreeInt.h +++ b/src/box/sql/btreeInt.h @@ -539,6 +539,7 @@ struct BtCursor { #define BTCF_Incrblob 0x10 /* True if an incremental I/O handle */ #define BTCF_Multiple 0x20 /* Maybe another cursor on the same btree */ #define BTCF_TaCursor 0x80 /* Tarantool cursor, pTaCursor valid */ +#define BTCF_TEphemCursor 0x40 /* Tarantool cursor to ephemeral table */ /* * Potential values for BtCursor.eState. diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c index 4ee5ac1d60..172509b10e 100644 --- a/src/box/sql/delete.c +++ b/src/box/sql/delete.c @@ -421,7 +421,7 @@ sqlite3DeleteFrom(Parse * pParse, /* The parser context */ pParse->nMem += nPk; iEphCur = pParse->nTab++; addrEphOpen = - sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEphCur, + sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur, nPk); sqlite3VdbeSetP4KeyInfo(pParse, pPk); } diff --git a/src/box/sql/opcodes.c b/src/box/sql/opcodes.c index 84fae8e70f..f27568be34 100644 --- a/src/box/sql/opcodes.c +++ b/src/box/sql/opcodes.c @@ -117,58 +117,59 @@ const char *sqlite3OpcodeName(int i){ /* 103 */ "OpenWrite" OpHelp("root=P2"), /* 104 */ "OpenAutoindex" OpHelp("nColumn=P2"), /* 105 */ "OpenEphemeral" OpHelp("nColumn=P2"), - /* 106 */ "SorterOpen" OpHelp(""), - /* 107 */ "SequenceTest" OpHelp("if (cursor[P1].ctr++) pc = P2"), - /* 108 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), - /* 109 */ "Close" OpHelp(""), - /* 110 */ "ColumnsUsed" OpHelp(""), - /* 111 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), - /* 112 */ "NextId" OpHelp("r[P3]=get_max(space_index[P1]{Column[P2]})"), - /* 113 */ "FCopy" OpHelp("reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)]"), - /* 114 */ "NewRowid" OpHelp("r[P2]=rowid"), + /* 106 */ "OpenTEphemeral" OpHelp("nColumn = P2"), + /* 107 */ "SorterOpen" OpHelp(""), + /* 108 */ "SequenceTest" OpHelp("if (cursor[P1].ctr++) pc = P2"), + /* 109 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), + /* 110 */ "Close" OpHelp(""), + /* 111 */ "ColumnsUsed" OpHelp(""), + /* 112 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), + /* 113 */ "NextId" OpHelp("r[P3]=get_max(space_index[P1]{Column[P2]})"), + /* 114 */ "FCopy" OpHelp("reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)]"), /* 115 */ "Real" OpHelp("r[P2]=P4"), - /* 116 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), - /* 117 */ "InsertInt" OpHelp("intkey=P3 data=r[P2]"), - /* 118 */ "Delete" OpHelp(""), - /* 119 */ "ResetCount" OpHelp(""), - /* 120 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), - /* 121 */ "SorterData" OpHelp("r[P2]=data"), - /* 122 */ "RowData" OpHelp("r[P2]=data"), - /* 123 */ "Rowid" OpHelp("r[P2]=rowid"), - /* 124 */ "NullRow" OpHelp(""), - /* 125 */ "SorterInsert" OpHelp("key=r[P2]"), - /* 126 */ "IdxInsert" OpHelp("key=r[P2]"), - /* 127 */ "IdxDelete" OpHelp("key=r[P2@P3]"), - /* 128 */ "Seek" OpHelp("Move P3 to P1.rowid"), - /* 129 */ "IdxRowid" OpHelp("r[P2]=rowid"), - /* 130 */ "Destroy" OpHelp(""), - /* 131 */ "Clear" OpHelp(""), - /* 132 */ "ResetSorter" OpHelp(""), - /* 133 */ "CreateIndex" OpHelp("r[P2]=root"), - /* 134 */ "CreateTable" OpHelp("r[P2]=root"), - /* 135 */ "ParseSchema2" OpHelp("rows=r[P1@P2]"), - /* 136 */ "ParseSchema3" OpHelp("name=r[P1] sql=r[P1+1]"), - /* 137 */ "RenameTable" OpHelp("P1 = root, P4 = name"), - /* 138 */ "LoadAnalysis" OpHelp(""), - /* 139 */ "DropTable" OpHelp(""), - /* 140 */ "DropIndex" OpHelp(""), - /* 141 */ "DropTrigger" OpHelp(""), - /* 142 */ "IntegrityCk" OpHelp(""), - /* 143 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), - /* 144 */ "Param" OpHelp(""), - /* 145 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), - /* 146 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), - /* 147 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), - /* 148 */ "AggStep0" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 149 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 150 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), - /* 151 */ "Expire" OpHelp(""), - /* 152 */ "Pagecount" OpHelp(""), - /* 153 */ "MaxPgcnt" OpHelp(""), - /* 154 */ "CursorHint" OpHelp(""), - /* 155 */ "IncMaxid" OpHelp(""), - /* 156 */ "Noop" OpHelp(""), - /* 157 */ "Explain" OpHelp(""), + /* 116 */ "NewRowid" OpHelp("r[P2]=rowid"), + /* 117 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), + /* 118 */ "InsertInt" OpHelp("intkey=P3 data=r[P2]"), + /* 119 */ "Delete" OpHelp(""), + /* 120 */ "ResetCount" OpHelp(""), + /* 121 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), + /* 122 */ "SorterData" OpHelp("r[P2]=data"), + /* 123 */ "RowData" OpHelp("r[P2]=data"), + /* 124 */ "Rowid" OpHelp("r[P2]=rowid"), + /* 125 */ "NullRow" OpHelp(""), + /* 126 */ "SorterInsert" OpHelp("key=r[P2]"), + /* 127 */ "IdxInsert" OpHelp("key=r[P2]"), + /* 128 */ "IdxDelete" OpHelp("key=r[P2@P3]"), + /* 129 */ "Seek" OpHelp("Move P3 to P1.rowid"), + /* 130 */ "IdxRowid" OpHelp("r[P2]=rowid"), + /* 131 */ "Destroy" OpHelp(""), + /* 132 */ "Clear" OpHelp(""), + /* 133 */ "ResetSorter" OpHelp(""), + /* 134 */ "CreateIndex" OpHelp("r[P2]=root"), + /* 135 */ "CreateTable" OpHelp("r[P2]=root"), + /* 136 */ "ParseSchema2" OpHelp("rows=r[P1@P2]"), + /* 137 */ "ParseSchema3" OpHelp("name=r[P1] sql=r[P1+1]"), + /* 138 */ "RenameTable" OpHelp("P1 = root, P4 = name"), + /* 139 */ "LoadAnalysis" OpHelp(""), + /* 140 */ "DropTable" OpHelp(""), + /* 141 */ "DropIndex" OpHelp(""), + /* 142 */ "DropTrigger" OpHelp(""), + /* 143 */ "IntegrityCk" OpHelp(""), + /* 144 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), + /* 145 */ "Param" OpHelp(""), + /* 146 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), + /* 147 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), + /* 148 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), + /* 149 */ "AggStep0" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 150 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 151 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), + /* 152 */ "Expire" OpHelp(""), + /* 153 */ "Pagecount" OpHelp(""), + /* 154 */ "MaxPgcnt" OpHelp(""), + /* 155 */ "CursorHint" OpHelp(""), + /* 156 */ "IncMaxid" OpHelp(""), + /* 157 */ "Noop" OpHelp(""), + /* 158 */ "Explain" OpHelp(""), }; return azName[i]; } diff --git a/src/box/sql/opcodes.h b/src/box/sql/opcodes.h index dfa19b7541..28cf70b7a2 100644 --- a/src/box/sql/opcodes.h +++ b/src/box/sql/opcodes.h @@ -106,58 +106,59 @@ #define OP_OpenWrite 103 /* synopsis: root=P2 */ #define OP_OpenAutoindex 104 /* synopsis: nColumn=P2 */ #define OP_OpenEphemeral 105 /* synopsis: nColumn=P2 */ -#define OP_SorterOpen 106 -#define OP_SequenceTest 107 /* synopsis: if (cursor[P1].ctr++) pc = P2 */ -#define OP_OpenPseudo 108 /* synopsis: P3 columns in r[P2] */ -#define OP_Close 109 -#define OP_ColumnsUsed 110 -#define OP_Sequence 111 /* synopsis: r[P2]=cursor[P1].ctr++ */ -#define OP_NextId 112 /* synopsis: r[P3]=get_max(space_index[P1]{Column[P2]}) */ -#define OP_FCopy 113 /* synopsis: reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)] */ -#define OP_NewRowid 114 /* synopsis: r[P2]=rowid */ +#define OP_OpenTEphemeral 106 /* synopsis: nColumn = P2 */ +#define OP_SorterOpen 107 +#define OP_SequenceTest 108 /* synopsis: if (cursor[P1].ctr++) pc = P2 */ +#define OP_OpenPseudo 109 /* synopsis: P3 columns in r[P2] */ +#define OP_Close 110 +#define OP_ColumnsUsed 111 +#define OP_Sequence 112 /* synopsis: r[P2]=cursor[P1].ctr++ */ +#define OP_NextId 113 /* synopsis: r[P3]=get_max(space_index[P1]{Column[P2]}) */ +#define OP_FCopy 114 /* synopsis: reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)] */ #define OP_Real 115 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ -#define OP_Insert 116 /* synopsis: intkey=r[P3] data=r[P2] */ -#define OP_InsertInt 117 /* synopsis: intkey=P3 data=r[P2] */ -#define OP_Delete 118 -#define OP_ResetCount 119 -#define OP_SorterCompare 120 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ -#define OP_SorterData 121 /* synopsis: r[P2]=data */ -#define OP_RowData 122 /* synopsis: r[P2]=data */ -#define OP_Rowid 123 /* synopsis: r[P2]=rowid */ -#define OP_NullRow 124 -#define OP_SorterInsert 125 /* synopsis: key=r[P2] */ -#define OP_IdxInsert 126 /* synopsis: key=r[P2] */ -#define OP_IdxDelete 127 /* synopsis: key=r[P2@P3] */ -#define OP_Seek 128 /* synopsis: Move P3 to P1.rowid */ -#define OP_IdxRowid 129 /* synopsis: r[P2]=rowid */ -#define OP_Destroy 130 -#define OP_Clear 131 -#define OP_ResetSorter 132 -#define OP_CreateIndex 133 /* synopsis: r[P2]=root */ -#define OP_CreateTable 134 /* synopsis: r[P2]=root */ -#define OP_ParseSchema2 135 /* synopsis: rows=r[P1@P2] */ -#define OP_ParseSchema3 136 /* synopsis: name=r[P1] sql=r[P1+1] */ -#define OP_RenameTable 137 /* synopsis: P1 = root, P4 = name */ -#define OP_LoadAnalysis 138 -#define OP_DropTable 139 -#define OP_DropIndex 140 -#define OP_DropTrigger 141 -#define OP_IntegrityCk 142 -#define OP_RowSetAdd 143 /* synopsis: rowset(P1)=r[P2] */ -#define OP_Param 144 -#define OP_FkCounter 145 /* synopsis: fkctr[P1]+=P2 */ -#define OP_MemMax 146 /* synopsis: r[P1]=max(r[P1],r[P2]) */ -#define OP_OffsetLimit 147 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ -#define OP_AggStep0 148 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggStep 149 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggFinal 150 /* synopsis: accum=r[P1] N=P2 */ -#define OP_Expire 151 -#define OP_Pagecount 152 -#define OP_MaxPgcnt 153 -#define OP_CursorHint 154 -#define OP_IncMaxid 155 -#define OP_Noop 156 -#define OP_Explain 157 +#define OP_NewRowid 116 /* synopsis: r[P2]=rowid */ +#define OP_Insert 117 /* synopsis: intkey=r[P3] data=r[P2] */ +#define OP_InsertInt 118 /* synopsis: intkey=P3 data=r[P2] */ +#define OP_Delete 119 +#define OP_ResetCount 120 +#define OP_SorterCompare 121 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ +#define OP_SorterData 122 /* synopsis: r[P2]=data */ +#define OP_RowData 123 /* synopsis: r[P2]=data */ +#define OP_Rowid 124 /* synopsis: r[P2]=rowid */ +#define OP_NullRow 125 +#define OP_SorterInsert 126 /* synopsis: key=r[P2] */ +#define OP_IdxInsert 127 /* synopsis: key=r[P2] */ +#define OP_IdxDelete 128 /* synopsis: key=r[P2@P3] */ +#define OP_Seek 129 /* synopsis: Move P3 to P1.rowid */ +#define OP_IdxRowid 130 /* synopsis: r[P2]=rowid */ +#define OP_Destroy 131 +#define OP_Clear 132 +#define OP_ResetSorter 133 +#define OP_CreateIndex 134 /* synopsis: r[P2]=root */ +#define OP_CreateTable 135 /* synopsis: r[P2]=root */ +#define OP_ParseSchema2 136 /* synopsis: rows=r[P1@P2] */ +#define OP_ParseSchema3 137 /* synopsis: name=r[P1] sql=r[P1+1] */ +#define OP_RenameTable 138 /* synopsis: P1 = root, P4 = name */ +#define OP_LoadAnalysis 139 +#define OP_DropTable 140 +#define OP_DropIndex 141 +#define OP_DropTrigger 142 +#define OP_IntegrityCk 143 +#define OP_RowSetAdd 144 /* synopsis: rowset(P1)=r[P2] */ +#define OP_Param 145 +#define OP_FkCounter 146 /* synopsis: fkctr[P1]+=P2 */ +#define OP_MemMax 147 /* synopsis: r[P1]=max(r[P1],r[P2]) */ +#define OP_OffsetLimit 148 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ +#define OP_AggStep0 149 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggStep 150 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggFinal 151 /* synopsis: accum=r[P1] N=P2 */ +#define OP_Expire 152 +#define OP_Pagecount 153 +#define OP_MaxPgcnt 154 +#define OP_CursorHint 155 +#define OP_IncMaxid 156 +#define OP_Noop 157 +#define OP_Explain 158 /* Properties such as "out2" or "jump" that are specified in ** comments following the "case" for each opcode in the vdbe.c @@ -183,13 +184,13 @@ /* 80 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,\ /* 88 */ 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,\ /* 96 */ 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\ -/* 104 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\ -/* 112 */ 0x20, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,\ -/* 120 */ 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x04, 0x00,\ -/* 128 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00,\ -/* 136 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,\ -/* 144 */ 0x10, 0x00, 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00,\ -/* 152 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,} +/* 104 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 112 */ 0x10, 0x20, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00,\ +/* 120 */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x04,\ +/* 128 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10,\ +/* 136 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 144 */ 0x06, 0x10, 0x00, 0x04, 0x1a, 0x00, 0x00, 0x00,\ +/* 152 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,} /* The sqlite3P2Values() routine is able to run faster if it knows ** the value of the largest JUMP opcode. The smaller the maximum diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h index acbadc5a1c..933bf25b81 100644 --- a/src/box/sql/tarantoolInt.h +++ b/src/box/sql/tarantoolInt.h @@ -85,6 +85,13 @@ int tarantoolSqlite3RenameTrigger(const char *zTriggerName, int tarantoolSqlite3RenameParentTable(int iTab, const char *zOldParentName, const char *zNewParentName); +/* Interface for ephemeral tables. */ +int tarantoolSqlite3EphemeralCreate(BtCursor * pCur, uint32_t filed_count); +int tarantoolSqlite3EphemeralInsert(BtCursor * pCur, const BtreePayload * pX); +int tarantoolSqlite3EphemeralFirst(BtCursor * pCur, int * pRes); +int tarantoolSqlite3EphemeralNext(BtCursor * pCur, int * pRes); +int tarantoolSqlite3EphemeralDrop(BtCursor * pCur); + /* Compare against the index key under a cursor - * the key may span non-adjacent fields in a random order, * ex: [4]-[1]-[2] diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index bd3d27e24f..497425a166 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -3451,6 +3451,55 @@ case OP_OpenEphemeral: { break; } +/* Opcode: OpenTEphemeral P1 P2 * * * + * Synopsis: nColumn = P2 + * + * This opcode creates Tarantool's ephemeral table and sets cursor P1 to it. + */ +case OP_OpenTEphemeral: { + VdbeCursor *pCx; + KeyInfo *pKeyInfo; + static const int vfsFlags = + SQLITE_OPEN_READWRITE | + SQLITE_OPEN_CREATE | + SQLITE_OPEN_EXCLUSIVE | + SQLITE_OPEN_DELETEONCLOSE | + SQLITE_OPEN_TRANSIENT_DB | + SQLITE_OPEN_MEMORY; + assert(pOp->p1 >= 0); + assert(pOp->p2 > 0); + + pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_BTREE); + if (pCx == 0) goto no_mem; + pCx->isEphemeral = 1; + pCx->nullRow = 1; + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBtx, + BTREE_OMIT_JOURNAL | BTREE_SINGLE, vfsFlags); + if (rc) goto abort_due_to_error; + rc = sqlite3BtreeBeginTrans(pCx->pBtx, 0, 1); + if (rc) goto abort_due_to_error; + if ((pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo) !=0) { + int pgno; + assert(pOp->p4type == P4_KEYINFO); + rc = sqlite3BtreeCreateTable(pCx->pBtx, &pgno, BTREE_BLOBKEY | pOp->p5); + if (rc == SQLITE_OK) { + assert(pgno == 2); + assert(pKeyInfo->db==db); + sqlite3BtreeCursorEphemeral(pCx->pBtx, pgno, BTREE_WRCSR, pKeyInfo, + pCx->uc.pCursor); + } + pCx->isTable = 0; + } else { + sqlite3BtreeCursorEphemeral(pCx->pBtx, 1, BTREE_WRCSR, 0, + pCx->uc.pCursor); + pCx->isTable = 1; + } + + rc = tarantoolSqlite3EphemeralCreate(pCx->uc.pCursor, pOp->p2); + if (rc) goto abort_due_to_error; + break; +} + /* Opcode: SorterOpen P1 P2 P3 P4 * * * This opcode works like OP_OpenEphemeral except that it opens -- GitLab