diff --git a/src/box/index.cc b/src/box/index.cc index 3c644f0a8ecae2b382c3cad4c86c138386845bf9..dfddeb32be3bcc0c06112220013eada45b506a34 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 d4b343b4abed03c42435b419047354ba0d108c82..53a56764871fecdf165e6d818ec889296c406c69 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 ca14ae6cd82460491b495e2121115a3797783a01..af735a3a2e1b6b13f90ce6afd30a15b48f2abb6a 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 2b5b687cdb1d4e58c2a466dc122b407d805ae485..df0f24f06935472d4edeab93db5319908a84ed92 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 fc4a6ca66e6dd19205a0476b5f605398bcd59931..4e6331c3b949692bea41cef49687c0eb29c52b7f 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 4ee5ac1d60fb01e961c5b5e057039575c6b01f5f..172509b10e383312c2368cb944e7d402ca929442 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 84fae8e70fa16ddaae2273a4da74fccbd68995a4..f27568be34bce182a5038bbaa36a1319a0ce5fe8 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 dfa19b75413dcf730d4a0388205def95b700df49..28cf70b7a26e8057637d6224382f6a2391ece3f9 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 acbadc5a1c8812bda91937976b947604e50946ac..933bf25b81b31f51fdc90e4896323d611aa518be 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 bd3d27e24f64317d885441616a4a3125c69cdb91..497425a166b6d9f60ff506fa303c90ed5b2d2b4a 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