From 2b5082ad2988aa9b466e87b0d764f7607ed3eff7 Mon Sep 17 00:00:00 2001 From: Kirill Yukhin <kyukhin@tarantool.org> Date: Thu, 17 Aug 2017 13:11:53 +0300 Subject: [PATCH] sql: Implement AUTOINCREMENT feature for int PKs Implement support for AUTOINCREMENT feature for integer primary keys of tables which lack rowid property. All tables which live in Tarantool's universe are lack rowid. To implement it two new op-codes were introduced: - Fetch maximum integer value of given space/index/fieldno - Copy register content from top frame into current frame Convert AUTOINCREMENT test coverage: sql-tap/autoinc.test Closes #2263 --- src/box/sql.c | 30 ++ src/box/sql/build.c | 23 +- src/box/sql/insert.c | 181 +++++-- src/box/sql/opcodes.c | 102 ++-- src/box/sql/opcodes.h | 115 ++--- src/box/sql/sqliteInt.h | 9 + src/box/sql/tarantoolInt.h | 8 + src/box/sql/vdbe.c | 63 +++ test/sql-tap/autoinc.test.lua | 901 ++++++++++++++++++++++++++++++++++ 9 files changed, 1261 insertions(+), 171 deletions(-) create mode 100755 test/sql-tap/autoinc.test.lua diff --git a/src/box/sql.c b/src/box/sql.c index 4415b53939..38eef7dfa9 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -1038,3 +1038,33 @@ sql_debug_info(struct info_handler *h) info_append_int(h, "sql_found_count", sql_found_count); info_end(h); } + +/** + * Extract maximum integer value from: + * @param index space_id + * @param index_id + * @param field number fieldno + * @param[out] fetched value in max_id + * + * @retval 0 on success, -1 otherwise. + * + * If index is empty - return 0 in max_id and success status + */ +int +tarantoolSqlGetMaxId(uint32_t space_id, uint32_t index_id, uint32_t fieldno, + uint64_t *max_id) +{ + char key[16]; + struct tuple *tuple; + char *key_end = mp_encode_array(key, 0); + if (box_index_max(space_id, index_id, key, key_end, &tuple) != 0) + return -1; + + /* Index is empty */ + if (tuple == NULL) { + *max_id = 0; + return 0; + } + + return tuple_field_u64(tuple, fieldno, max_id); +} diff --git a/src/box/sql/build.c b/src/box/sql/build.c index 8adf054c40..848c6796cd 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -851,12 +851,12 @@ void sqlite3StartTable( assert( pParse->pNewTable==0 ); pParse->pNewTable = pTable; - /* If this is the magic sqlite_sequence table used by autoincrement, + /* If this is the magic sql_sequence table used by autoincrement, ** then record a pointer to this table in the main database structure ** so that INSERT can find the table easily. */ #ifndef SQLITE_OMIT_AUTOINCREMENT - if( !pParse->nested && strcmp(zName, "sqlite_sequence")==0 ){ + if( !pParse->nested && strcmp(zName, "sql_sequence")==0 ){ assert( sqlite3SchemaMutexHeld(db, 0) ); pTable->pSchema->pSeqTab = pTable; } @@ -1191,12 +1191,14 @@ void sqlite3AddPrimaryKey( } } } + pTab->iAutoIncPKey = -1; if( nTerm==1 && pCol && sqlite3StrICmp(sqlite3ColumnType(pCol,""), "INTEGER")==0 && sortOrder!=SQLITE_SO_DESC ){ pTab->iPKey = iCol; + pTab->iAutoIncPKey = iCol; pTab->keyConf = (u8)onError; assert( autoInc==0 || autoInc==1 ); pTab->tabFlags |= autoInc*TF_Autoincrement; @@ -1954,11 +1956,6 @@ void sqlite3EndTable( /* Special processing for WITHOUT ROWID Tables */ if( tabOpts & TF_WithoutRowid ){ - if( (p->tabFlags & TF_Autoincrement) ){ - sqlite3ErrorMsg(pParse, - "AUTOINCREMENT not allowed on WITHOUT ROWID tables"); - return; - } if( (p->tabFlags & TF_HasPrimaryKey)==0 ){ sqlite3ErrorMsg(pParse, "PRIMARY KEY missing on table %s", p->zName); }else{ @@ -2110,7 +2107,7 @@ void sqlite3EndTable( sqlite3VdbeAddOp1(v, OP_Close, iCursor); #ifndef SQLITE_OMIT_AUTOINCREMENT - /* Check to see if we need to create an sqlite_sequence table for + /* Check to see if we need to create an sql_sequence table for ** keeping track of autoincrement keys. */ if( (p->tabFlags & TF_Autoincrement)!=0 ){ @@ -2118,7 +2115,7 @@ void sqlite3EndTable( assert( sqlite3SchemaMutexHeld(db, 0) ); if( pDb->pSchema->pSeqTab==0 ){ sqlite3NestedParse(pParse, - "CREATE TABLE sqlite_sequence(name,seq)" + "CREATE TABLE sql_sequence(name PRIMARY KEY,seq)" ); } } @@ -2466,14 +2463,14 @@ void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int isView){ pParse->nested--; #ifndef SQLITE_OMIT_AUTOINCREMENT - /* Remove any entries of the sqlite_sequence table associated with + /* Remove any entries of the sql_sequence table associated with ** the table being dropped. This is done before the table is dropped - ** at the btree level, in case the sqlite_sequence table needs to + ** at the btree level, in case the sql_sequence table needs to ** move as a result of the drop (can happen in auto-vacuum mode). */ if( pTab->tabFlags & TF_Autoincrement ){ sqlite3NestedParse(pParse, - "DELETE FROM sqlite_sequence WHERE name=%Q", + "DELETE FROM sql_sequence WHERE name=%Q", pTab->zName ); } @@ -2591,7 +2588,7 @@ void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){ } } #endif - if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 + if( sqlite3StrNICmp(pTab->zName, "sql_sequence", 12)==0 && sqlite3StrNICmp(pTab->zName, "sqlite_stat", 11)!=0 ){ sqlite3ErrorMsg(pParse, "table %s may not be dropped", pTab->zName); goto exit_drop_table; diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c index d7cab4a094..345afcc27a 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -24,11 +24,11 @@ ** for that table that is actually opened. */ void -sqlite3OpenTable(Parse * pParse, /* Generate code into this VDBE */ - int iCur, /* The cursor number of the table */ - Table * pTab, /* The table to be opened */ - int opcode /* OP_OpenRead or OP_OpenWrite */ - ) +sqlite3OpenTable(Parse * pParse, /* Generate code into this VDBE */ + int iCur, /* The cursor number of the table */ + Table * pTab, /* The table to be opened */ + int opcode /* OP_OpenRead or OP_OpenWrite */ + ) { Vdbe *v; assert(!IsVirtual(pTab)); @@ -228,7 +228,7 @@ readsTable(Parse * p, Table * pTab) ** ** (1) Register to hold the name of the pTab table. ** (2) Register to hold the maximum ROWID of pTab. -** (3) Register to hold the rowid in sqlite_sequence of pTab +** (3) Register to hold the rowid in sql_sequence of pTab ** ** The 2nd register is the one that is returned. That is all the ** insert routine needs to know about. @@ -256,9 +256,9 @@ autoIncBegin(Parse * pParse, /* Parsing context */ pInfo->pNext = pToplevel->pAinc; pToplevel->pAinc = pInfo; pInfo->pTab = pTab; - pToplevel->nMem++; /* Register to hold name of table */ - pInfo->regCtr = ++pToplevel->nMem; /* Max rowid register */ - pToplevel->nMem++; /* Rowid in sqlite_sequence */ + pToplevel->nMem++; /* Register to hold name of table */ + pInfo->regCtr = ++pToplevel->nMem; /* Max rowid register */ + pToplevel->nMem++; /* Rowid in sql_sequence */ } memId = pInfo->regCtr; } @@ -290,13 +290,12 @@ sqlite3AutoincrementBegin(Parse * pParse) /* 0 */ {OP_Null, 0, 0, 0}, /* 1 */ {OP_Rewind, 0, 9, 0}, /* 2 */ {OP_Column, 0, 0, 0}, - /* 3 */ {OP_Ne, 0, 7, 0}, - /* 4 */ {OP_Rowid, 0, 0, 0}, - /* 5 */ {OP_Column, 0, 1, 0}, - /* 6 */ {OP_Goto, 0, 9, 0}, - /* 7 */ {OP_Next, 0, 2, 0}, - /* 8 */ {OP_Integer, 0, 0, 0}, - /* 9 */ {OP_Close, 0, 0, 0} + /* 3 */ {OP_Ne, 0, 6, 0}, + /* 4 */ {OP_Column, 0, 1, 0}, + /* 5 */ {OP_Goto, 0, 8, 0}, + /* 6 */ {OP_Next, 0, 2, 0}, + /* 7 */ {OP_Integer, 0, 0, 0}, + /* 8 */ {OP_Close, 0, 0, 0} }; VdbeOp *aOp; pDb = &db->mdb; @@ -314,9 +313,8 @@ sqlite3AutoincrementBegin(Parse * pParse) aOp[3].p1 = memId - 1; aOp[3].p3 = memId; aOp[3].p5 = SQLITE_JUMPIFNULL; - aOp[4].p2 = memId + 1; - aOp[5].p3 = memId; - aOp[8].p2 = memId; + aOp[4].p3 = memId; + aOp[7].p2 = memId; } } @@ -338,7 +336,7 @@ autoIncStep(Parse * pParse, int memId, int regRowid) /* ** This routine generates the code needed to write autoincrement -** maximum rowid values back into the sqlite_sequence register. +** maximum rowid values back into the sql_sequence register. ** Every statement that might do an INSERT into an autoincrement ** table (either directly or through triggers) needs to call this ** routine just before the "exit" code. @@ -354,11 +352,12 @@ autoIncrementEnd(Parse * pParse) for (p = pParse->pAinc; p; p = p->pNext) { static const int iLn = VDBE_OFFSET_LINENO(2); static const VdbeOpList autoIncEnd[] = { - /* 0 */ {OP_NotNull, 0, 2, 0}, - /* 1 */ {OP_NewRowid, 0, 0, 0}, - /* 2 */ {OP_MakeRecord, 0, 2, 0}, - /* 3 */ {OP_Insert, 0, 0, 0}, - /* 4 */ {OP_Close, 0, 0, 0} + /* 0 */ {OP_SeekGE, 0, 3, 0}, + /* 1 */ {OP_IdxGT, 0, 3, 0}, + /* 2 */ {OP_Delete, 0, 0, 0}, + /* 3 */ {OP_MakeRecord, 0, 2, 0}, + /* 4 */ {OP_IdxInsert, 0, 0, 0}, + /* 5 */ {OP_Close, 0, 0, 0} }; VdbeOp *aOp; Db *pDb = &db->mdb; @@ -374,13 +373,18 @@ autoIncrementEnd(Parse * pParse) iLn); if (aOp == 0) break; - aOp[0].p1 = memId + 1; - aOp[1].p2 = memId + 1; - aOp[2].p1 = memId - 1; - aOp[2].p3 = iRec; - aOp[3].p2 = iRec; - aOp[3].p3 = memId + 1; - aOp[3].p5 = OPFLAG_APPEND; + aOp[0].p3 = memId - 1; + aOp[0].p4.i = 1; + aOp[0].p4type = P4_INT32; + aOp[1].p3 = memId - 1; + aOp[1].p4.i = 1; + aOp[1].p4type = P4_INT32; + aOp[2].p2 = memId + 1; + aOp[3].p1 = memId - 1; + aOp[3].p3 = iRec; + aOp[4].p2 = iRec; + aOp[4].p3 = memId + 1; + aOp[4].p5 = OPFLAG_APPEND; sqlite3ReleaseTempReg(pParse, iRec); } } @@ -541,6 +545,8 @@ sqlite3Insert(Parse * pParse, /* Parser context */ int regIns; /* Block of regs holding rowid+data being inserted */ int regRowid; /* registers holding insert rowid */ int regData; /* register holding first column to insert */ + int regPK; /* register containing PK for autoinc prepared + for MakeRecord */ int *aRegIdx = 0; /* One register allocated to each index */ #ifndef SQLITE_OMIT_TRIGGER @@ -641,7 +647,7 @@ sqlite3Insert(Parse * pParse, /* Parser context */ #endif /* SQLITE_OMIT_XFER_OPT */ /* If this is an AUTOINCREMENT table, look up the sequence number in the - ** sqlite_sequence table and store it in memory cell regAutoinc. + ** sql_sequence table and store it in memory cell regAutoinc. */ regAutoinc = autoIncBegin(pParse, pTab); @@ -1014,7 +1020,11 @@ sqlite3Insert(Parse * pParse, /* Parser context */ VdbeCoverage(v); } } else if (IsVirtual(pTab) || withoutRowid) { - sqlite3VdbeAddOp2(v, OP_Null, 0, regRowid); + if (pTab->iAutoIncPKey >= 0) + sqlite3VdbeAddOp3(v, OP_MaxId, iDataCur, + pTab->iAutoIncPKey, regRowid); + else + sqlite3VdbeAddOp2(v, OP_Null, 0, regRowid); } else { sqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc); @@ -1026,16 +1036,26 @@ sqlite3Insert(Parse * pParse, /* Parser context */ ** with the first column. */ nHidden = 0; + regPK = -1; for (i = 0; i < pTab->nCol; i++) { int iRegStore = regRowid + 1 + i; - if (i == pTab->iPKey) { - /* The value of the INTEGER PRIMARY KEY column is always a NULL. - ** Whenever this column is read, the rowid will be substituted - ** in its place. Hence, fill this column with a NULL to avoid - ** taking up data space with information that will never be used. - ** As there may be shallow copies of this value, make it a soft-NULL */ - sqlite3VdbeAddOp1(v, OP_SoftNull, iRegStore); - continue; + if (i == pTab->iAutoIncPKey) { + /* PK was not specified in IDLIST and marked for + * autoincrementation. Extract max ID from the + * index and increment it. This'll be compared + * w/ corresponding value extracted from + * sql_sequence for given index later. + */ + if ((pTab->tabFlags & TF_Autoincrement) + && (ipkColumn == -1)) { + sqlite3VdbeAddOp2(v, + OP_FCopy, + regAutoinc, + iRegStore); + sqlite3VdbeAddOp2(v, OP_AddImm, + iRegStore, 1); + } + regPK = iRegStore; } if (pColumn == 0) { if (IsHiddenColumn(&pTab->aCol[i])) { @@ -1052,27 +1072,84 @@ sqlite3Insert(Parse * pParse, /* Parser context */ } if (j < 0 || nColumn == 0 || (pColumn && j >= pColumn->nId)) { + if (i == pTab->iAutoIncPKey) continue; sqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore); - } else if (useTempTable) { - sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, - iRegStore); + } else if (useTempTable) { + if ((pTab->tabFlags & TF_Autoincrement) + && (i == pTab->iAutoIncPKey)) { + int regTmp = ++pParse->nMem ; + /* Emit code which doesn't override + * autoinc-ed value with select result + * in case if result is NULL value. + */ + sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, regTmp); + sqlite3VdbeAddOp2(v, OP_FCopy, regTmp, iRegStore); + sqlite3VdbeChangeP3(v, -1, OPFLAG_SAME_FRAME | OPFLAG_NOOP_IF_NULL); + } else { + sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, + iRegStore); + } } else if (pSelect) { if (regFromSelect != regData) { - sqlite3VdbeAddOp2(v, OP_SCopy, - regFromSelect + j, - iRegStore); + if ((pTab->tabFlags & TF_Autoincrement) + && (i == pTab->iAutoIncPKey)) { + /* Emit code which doesn't override + * autoinc-ed value with select result + * in case that result is NULL + */ + sqlite3VdbeAddOp2(v, OP_FCopy, + regFromSelect + j, + iRegStore); + sqlite3VdbeChangeP3(v, + -1, + OPFLAG_SAME_FRAME + | OPFLAG_NOOP_IF_NULL); + } else { + sqlite3VdbeAddOp2(v, OP_SCopy, + regFromSelect + j, + iRegStore); + } } } else { + if (i == pTab->iAutoIncPKey) { + if (pList->a[j].pExpr->op == TK_NULL) + continue; + + if (pList->a[j].pExpr->op == TK_REGISTER) { + /* Emit code which doesn't override + * autoinc-ed value with select result + * in case that result is NULL + */ + sqlite3VdbeAddOp2(v, OP_FCopy, + pList->a[j].pExpr->iTable, + iRegStore); + sqlite3VdbeChangeP3(v, + -1, + OPFLAG_SAME_FRAME + | OPFLAG_NOOP_IF_NULL); + continue; + } + } + sqlite3ExprCode(pParse, pList->a[j].pExpr, iRegStore); } } - /* Generate code to check constraints and generate index keys and - ** do the insertion. - */ + /* If value in regAutoinc exceeds one contained in + regPK, need to update it. */ + if (pTab->tabFlags & TF_Autoincrement) { + /* No way there's no PK in the table. */ + assert( regPK >= 0); + + autoIncStep(pParse, regAutoinc, regPK); + } + + /* Generate code to check constraints and generate index keys + and do the insertion. + */ #ifndef SQLITE_OMIT_VIRTUALTABLE if (IsVirtual(pTab)) { const char *pVTab = @@ -1147,7 +1224,7 @@ sqlite3Insert(Parse * pParse, /* Parser context */ } insert_end: - /* Update the sqlite_sequence table by storing the content of the + /* Update the sql_sequence table by storing the content of the ** maximum rowid counter values recorded while inserting into ** autoincrement tables. */ diff --git a/src/box/sql/opcodes.c b/src/box/sql/opcodes.c index 804c5a0fa5..5d3b25e4b9 100644 --- a/src/box/sql/opcodes.c +++ b/src/box/sql/opcodes.c @@ -126,57 +126,59 @@ const char *sqlite3OpcodeName(int i){ /* 112 */ "Close" OpHelp(""), /* 113 */ "ColumnsUsed" OpHelp(""), /* 114 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), - /* 115 */ "NewRowid" OpHelp("r[P2]=rowid"), - /* 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(""), + /* 115 */ "MaxId" OpHelp("r[P3]=get_max(space_index[P1]{Column[P2]})"), + /* 116 */ "FCopy" OpHelp("reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)]"), + /* 117 */ "NewRowid" OpHelp("r[P2]=rowid"), + /* 118 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), + /* 119 */ "InsertInt" OpHelp("intkey=P3 data=r[P2]"), + /* 120 */ "Delete" OpHelp(""), + /* 121 */ "ResetCount" OpHelp(""), + /* 122 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), + /* 123 */ "SorterData" OpHelp("r[P2]=data"), + /* 124 */ "RowData" OpHelp("r[P2]=data"), + /* 125 */ "Rowid" OpHelp("r[P2]=rowid"), + /* 126 */ "NullRow" OpHelp(""), + /* 127 */ "SorterInsert" OpHelp("key=r[P2]"), + /* 128 */ "IdxInsert" OpHelp("key=r[P2]"), + /* 129 */ "IdxDelete" OpHelp("key=r[P2@P3]"), + /* 130 */ "Seek" OpHelp("Move P3 to P1.rowid"), /* 131 */ "Real" OpHelp("r[P2]=P4"), - /* 132 */ "Clear" OpHelp(""), - /* 133 */ "ResetSorter" OpHelp(""), - /* 134 */ "CreateIndex" OpHelp("r[P2]=root iDb=P1"), - /* 135 */ "CreateTable" OpHelp("r[P2]=root iDb=P1"), - /* 136 */ "ParseSchema" OpHelp(""), - /* 137 */ "ParseSchema2" OpHelp("rows=r[P1@P2] iDb=P3"), - /* 138 */ "ParseSchema3" OpHelp("name=r[P1] sql=r[P1+1] iDb=P2"), - /* 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 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), - /* 154 */ "VBegin" OpHelp(""), - /* 155 */ "VCreate" OpHelp(""), - /* 156 */ "VDestroy" OpHelp(""), - /* 157 */ "VOpen" OpHelp(""), - /* 158 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), - /* 159 */ "VRename" OpHelp(""), - /* 160 */ "Pagecount" OpHelp(""), - /* 161 */ "MaxPgcnt" OpHelp(""), - /* 162 */ "CursorHint" OpHelp(""), - /* 163 */ "IncMaxid" OpHelp(""), - /* 164 */ "Noop" OpHelp(""), - /* 165 */ "Explain" OpHelp(""), + /* 132 */ "IdxRowid" OpHelp("r[P2]=rowid"), + /* 133 */ "Destroy" OpHelp(""), + /* 134 */ "Clear" OpHelp(""), + /* 135 */ "ResetSorter" OpHelp(""), + /* 136 */ "CreateIndex" OpHelp("r[P2]=root iDb=P1"), + /* 137 */ "CreateTable" OpHelp("r[P2]=root iDb=P1"), + /* 138 */ "ParseSchema" OpHelp(""), + /* 139 */ "ParseSchema2" OpHelp("rows=r[P1@P2] iDb=P3"), + /* 140 */ "ParseSchema3" OpHelp("name=r[P1] sql=r[P1+1] iDb=P2"), + /* 141 */ "LoadAnalysis" OpHelp(""), + /* 142 */ "DropTable" OpHelp(""), + /* 143 */ "DropIndex" OpHelp(""), + /* 144 */ "DropTrigger" OpHelp(""), + /* 145 */ "IntegrityCk" OpHelp(""), + /* 146 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), + /* 147 */ "Param" OpHelp(""), + /* 148 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), + /* 149 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), + /* 150 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), + /* 151 */ "AggStep0" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 152 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 153 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), + /* 154 */ "Expire" OpHelp(""), + /* 155 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), + /* 156 */ "VBegin" OpHelp(""), + /* 157 */ "VCreate" OpHelp(""), + /* 158 */ "VDestroy" OpHelp(""), + /* 159 */ "VOpen" OpHelp(""), + /* 160 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), + /* 161 */ "VRename" OpHelp(""), + /* 162 */ "Pagecount" OpHelp(""), + /* 163 */ "MaxPgcnt" OpHelp(""), + /* 164 */ "CursorHint" OpHelp(""), + /* 165 */ "IncMaxid" OpHelp(""), + /* 166 */ "Noop" OpHelp(""), + /* 167 */ "Explain" OpHelp(""), }; return azName[i]; } diff --git a/src/box/sql/opcodes.h b/src/box/sql/opcodes.h index 578783e165..5992f8d063 100644 --- a/src/box/sql/opcodes.h +++ b/src/box/sql/opcodes.h @@ -115,57 +115,59 @@ #define OP_Close 112 #define OP_ColumnsUsed 113 #define OP_Sequence 114 /* synopsis: r[P2]=cursor[P1].ctr++ */ -#define OP_NewRowid 115 /* synopsis: r[P2]=rowid */ -#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_MaxId 115 /* synopsis: r[P3]=get_max(space_index[P1]{Column[P2]}) */ +#define OP_FCopy 116 /* synopsis: reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)] */ +#define OP_NewRowid 117 /* synopsis: r[P2]=rowid */ +#define OP_Insert 118 /* synopsis: intkey=r[P3] data=r[P2] */ +#define OP_InsertInt 119 /* synopsis: intkey=P3 data=r[P2] */ +#define OP_Delete 120 +#define OP_ResetCount 121 +#define OP_SorterCompare 122 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ +#define OP_SorterData 123 /* synopsis: r[P2]=data */ +#define OP_RowData 124 /* synopsis: r[P2]=data */ +#define OP_Rowid 125 /* synopsis: r[P2]=rowid */ +#define OP_NullRow 126 +#define OP_SorterInsert 127 /* synopsis: key=r[P2] */ +#define OP_IdxInsert 128 /* synopsis: key=r[P2] */ +#define OP_IdxDelete 129 /* synopsis: key=r[P2@P3] */ +#define OP_Seek 130 /* synopsis: Move P3 to P1.rowid */ #define OP_Real 131 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ -#define OP_Clear 132 -#define OP_ResetSorter 133 -#define OP_CreateIndex 134 /* synopsis: r[P2]=root iDb=P1 */ -#define OP_CreateTable 135 /* synopsis: r[P2]=root iDb=P1 */ -#define OP_ParseSchema 136 -#define OP_ParseSchema2 137 /* synopsis: rows=r[P1@P2] iDb=P3 */ -#define OP_ParseSchema3 138 /* synopsis: name=r[P1] sql=r[P1+1] iDb=P2 */ -#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_TableLock 153 /* synopsis: iDb=P1 root=P2 write=P3 */ -#define OP_VBegin 154 -#define OP_VCreate 155 -#define OP_VDestroy 156 -#define OP_VOpen 157 -#define OP_VColumn 158 /* synopsis: r[P3]=vcolumn(P2) */ -#define OP_VRename 159 -#define OP_Pagecount 160 -#define OP_MaxPgcnt 161 -#define OP_CursorHint 162 -#define OP_IncMaxid 163 -#define OP_Noop 164 -#define OP_Explain 165 +#define OP_IdxRowid 132 /* synopsis: r[P2]=rowid */ +#define OP_Destroy 133 +#define OP_Clear 134 +#define OP_ResetSorter 135 +#define OP_CreateIndex 136 /* synopsis: r[P2]=root iDb=P1 */ +#define OP_CreateTable 137 /* synopsis: r[P2]=root iDb=P1 */ +#define OP_ParseSchema 138 +#define OP_ParseSchema2 139 /* synopsis: rows=r[P1@P2] iDb=P3 */ +#define OP_ParseSchema3 140 /* synopsis: name=r[P1] sql=r[P1+1] iDb=P2 */ +#define OP_LoadAnalysis 141 +#define OP_DropTable 142 +#define OP_DropIndex 143 +#define OP_DropTrigger 144 +#define OP_IntegrityCk 145 +#define OP_RowSetAdd 146 /* synopsis: rowset(P1)=r[P2] */ +#define OP_Param 147 +#define OP_FkCounter 148 /* synopsis: fkctr[P1]+=P2 */ +#define OP_MemMax 149 /* synopsis: r[P1]=max(r[P1],r[P2]) */ +#define OP_OffsetLimit 150 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ +#define OP_AggStep0 151 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggStep 152 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggFinal 153 /* synopsis: accum=r[P1] N=P2 */ +#define OP_Expire 154 +#define OP_TableLock 155 /* synopsis: iDb=P1 root=P2 write=P3 */ +#define OP_VBegin 156 +#define OP_VCreate 157 +#define OP_VDestroy 158 +#define OP_VOpen 159 +#define OP_VColumn 160 /* synopsis: r[P3]=vcolumn(P2) */ +#define OP_VRename 161 +#define OP_Pagecount 162 +#define OP_MaxPgcnt 163 +#define OP_CursorHint 164 +#define OP_IncMaxid 165 +#define OP_Noop 166 +#define OP_Explain 167 /* Properties such as "out2" or "jump" that are specified in ** comments following the "case" for each opcode in the vdbe.c @@ -192,13 +194,14 @@ /* 88 */ 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,\ /* 96 */ 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00,\ /* 104 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 112 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,\ -/* 120 */ 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x04, 0x00,\ -/* 128 */ 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10,\ -/* 136 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 144 */ 0x06, 0x10, 0x00, 0x04, 0x1a, 0x00, 0x00, 0x00,\ +/* 112 */ 0x00, 0x00, 0x10, 0x20, 0x10, 0x10, 0x00, 0x00,\ +/* 120 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x04,\ +/* 128 */ 0x04, 0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00,\ +/* 136 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 144 */ 0x00, 0x00, 0x06, 0x10, 0x00, 0x04, 0x1a, 0x00,\ /* 152 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 160 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,} +/* 160 */ 0x00, 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/sqliteInt.h b/src/box/sql/sqliteInt.h index eed00980e1..e48d549a6c 100644 --- a/src/box/sql/sqliteInt.h +++ b/src/box/sql/sqliteInt.h @@ -1785,6 +1785,8 @@ struct Table { int tnum; /* Root BTree page for this table */ u32 nTabRef; /* Number of pointers to this Table */ i16 iPKey; /* If not negative, use aCol[iPKey] as the rowid */ + i16 iAutoIncPKey; /* If PK is marked INTEGER PRIMARY KEY AUTOINCREMENT, store + column number here, -1 otherwise Tarantool specifics */ i16 nCol; /* Number of columns in this table */ LogEst nRowLogEst; /* Estimated rows in table - from sqlite_stat1 table */ LogEst szTabRow; /* Estimated size of each table row in bytes */ @@ -3007,6 +3009,13 @@ struct AuthContext { #define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete: keep cursor position */ #define OPFLAG_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */ +#define OPFLAG_SAME_FRAME 0x01 /* OP_FCopy: use same frame for source + * register + */ +#define OPFLAG_NOOP_IF_NULL 0x02 /* OP_FCopy: if source register is NULL + * then do nothing + */ + /* * Each trigger present in the database schema is stored as an instance of * struct Trigger. diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h index 3f8863bdbe..ba75d5e680 100644 --- a/src/box/sql/tarantoolInt.h +++ b/src/box/sql/tarantoolInt.h @@ -6,6 +6,8 @@ ** that's why we are using a weird naming schema. */ +#include <stdint.h> + /* * Tarantool system spaces. */ @@ -96,3 +98,9 @@ int tarantoolSqlite3MakeIdxParts(Index *index, void *buf); * If buf==NULL estimate result size. */ int tarantoolSqlite3MakeIdxOpts(Index *index, const char *zSql, void *buf); + +/* + * Fetch maximum value from ineger column number `fieldno` of space_id/index_id + * Return 0 on success, -1 otherwise + */ +int tarantoolSqlGetMaxId(uint32_t space_id, uint32_t index_id, uint32_t fieldno, uint64_t *max_id); diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index cef983eedf..4db3e60cba 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -4056,6 +4056,69 @@ case OP_Sequence: { /* out2 */ break; } +/* Opcode: MaxId P1 P2 P3 * * +** Synopsis: r[P3]=get_max(space_index[P1]{Column[P2]}) +** +** Get next Id of the table. P1 is a table cursor, P2 is column +** number. Return in P3 maximum id found in provided column. +** +** This opcode is Tarantool specific and will segfault in case +** of SQLite cursor. +*/ +case OP_MaxId: { /* out3 */ + VdbeCursor *pC; /* The VDBE cursor */ + int p2; /* Column number, which stores the id */ + int pgno; /* Page number of the cursor */ + pC = p->apCsr[pOp->p1]; + p2 = pOp->p2; + pOut = &aMem[pOp->p3]; + + /* This opcode is Tarantool specific. */ + assert( pC->uc.pCursor->curFlags & BTCF_TaCursor ); + + pgno = pC->pgnoRoot; + + tarantoolSqlGetMaxId(SQLITE_PAGENO_TO_SPACEID(pgno), SQLITE_PAGENO_TO_INDEXID(pgno), p2, &pOut->u.i); + + pOut->flags = MEM_Int; + break; +} + +/* Opcode: FCopy P1 P2 P3 * * +** Synopsis: reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)] +** +** Copy integer value of register P1 in root frame in to register P2 of current +** frame. If current frame is topmost - copy within signle frame. +** Source register must hold integer value. +** +** If P3's flag OPFLAG_SAME_FRAME is set, do shallow copy of register within +** same frame, still making sure the value is integer. +** +** If P3's flag OPFLAG_NOOP_IF_NULL is set, then do nothing if reg[P1] is NULL +*/ +case OP_FCopy: { /* out2 */ + VdbeFrame *pFrame; + Mem *pIn1, *pOut; + if( p->pFrame && ((pOp->p3 & OPFLAG_SAME_FRAME) == 0)) { + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + pIn1 = &pFrame->aMem[pOp->p1]; + }else{ + pIn1 = &aMem[pOp->p1]; + } + + if ((pOp->p3 & OPFLAG_NOOP_IF_NULL) && (pIn1->flags & MEM_Null)) { + /* Flag is set and register is NULL -> do nothing */ + } else { + assert( memIsValid(pIn1) ); + assert( pIn1->flags & MEM_Int); + + pOut = &aMem[pOp->p2]; + MemSetTypeFlag(pOut, MEM_Int); + + pOut->u.i = pIn1->u.i; + } + break; +} /* Opcode: NewRowid P1 P2 P3 * * ** Synopsis: r[P2]=rowid diff --git a/test/sql-tap/autoinc.test.lua b/test/sql-tap/autoinc.test.lua new file mode 100755 index 0000000000..8c2dc2a802 --- /dev/null +++ b/test/sql-tap/autoinc.test.lua @@ -0,0 +1,901 @@ +#!/usr/bin/env tarantool +test = require("sqltester") +test:plan(53) + +--!./tcltestrunner.lua +-- 2004 November 12 +-- +-- The author disclaims copyright to this source code. In place of +-- a legal notice, here is a blessing: +-- +-- May you do good and not evil. +-- May you find forgiveness for yourself and forgive others. +-- May you share freely, never taking more than you give. +-- +--------------------------------------------------------------------------- +-- This file implements regression tests for SQLite library. The +-- focus of this script is testing the AUTOINCREMENT features. +-- +-- $Id: autoinc.test,v 1.14 2009/06/23 20:28:54 drh Exp $ +-- +-- ["set","testdir",[["file","dirname",["argv0"]]]] +-- ["source",[["testdir"],"\/tester.tcl"]] +-- If the library is not compiled with autoincrement support then +-- skip all tests in this file. +-- + +-- Add a table with the AUTOINCREMENT feature. Verify that the +-- SQLITE_SEQUENCE table gets created. +-- +test:do_execsql_test( + "autoinc-1.2", + [[ + CREATE TABLE t1(x INTEGER PRIMARY KEY AUTOINCREMENT, y); + ]], { + -- <autoinc-1.2> + + -- </autoinc-1.2> + }) + +-- The SQLITE_SEQUENCE table is initially empty +-- +test:do_execsql_test( + "autoinc-1.3", + [[ + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-1.3> + + -- </autoinc-1.3> + }) + +-- Close and reopen the database. Verify that everything is still there. +-- +-- test:do_test( +-- "autoinc-1.4", +-- function() +-- db("close") +-- sqlite3("db", "test.db") +-- return test:execsql([[ +-- SELECT * FROM sql_sequence; +-- ]]) +-- end, { +-- -- <autoinc-1.4> + +-- -- </autoinc-1.4> +-- }) + +-- We are not allowed to drop the sqlite_sequence table. +-- +test:do_catchsql_test( + "autoinc-1.5", + [[ + DROP TABLE sql_sequence + ]], { + -- <autoinc-1.5> + 1, "table sql_sequence may not be dropped" + -- </autoinc-1.5> + }) + +test:do_execsql_test( + "autoinc-1.6", + [[ + SELECT name FROM _space WHERE name NOT IN (SELECT name FROM _space WHERE name LIKE '\_%' ESCAPE '\') + ]], { + -- <autoinc-1.6> + "t1", "sql_sequence" + -- </autoinc-1.6> + }) + +-- Insert an entries into the t1 table and make sure the largest key +-- is always recorded in the sqlite_sequence table. +-- +test:do_execsql_test( + "autoinc-2.1", + [[ + --SELECT * FROM sql_sequence + ]], { + -- <autoinc-2.1> + + -- </autoinc-2.1> + }) + +test:do_execsql_test( + "autoinc-2.2", + [[ + INSERT INTO t1 VALUES(12,34); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.2> + "t1", 12 + -- </autoinc-2.2> + }) + +test:do_execsql_test( + "autoinc-2.3", + [[ + INSERT INTO t1 VALUES(1,23); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.3> + "t1", 12 + -- </autoinc-2.3> + }) + +test:do_execsql_test( + "autoinc-2.4", + [[ + INSERT INTO t1 VALUES(123,456); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.4> + "t1", 123 + -- </autoinc-2.4> + }) + +test:do_execsql_test( + "autoinc-2.5", + [[ + INSERT INTO t1 VALUES(NULL,567); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.5> + "t1", 124 + -- </autoinc-2.5> + }) + +test:do_execsql_test( + "autoinc-2.6", + [[ + DELETE FROM t1 WHERE y=567; + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.6> + "t1", 124 + -- </autoinc-2.6> + }) + +test:do_execsql_test( + "autoinc-2.7", + [[ + INSERT INTO t1 VALUES(NULL,567); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.7> + "t1", 125 + -- </autoinc-2.7> + }) + +test:do_execsql_test( + "autoinc-2.8", + [[ + DELETE FROM t1; + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.8> + "t1", 125 + -- </autoinc-2.8> + }) + +test:do_execsql_test( + "autoinc-2.9", + [[ + INSERT INTO t1 VALUES(12,34); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.9> + "t1", 125 + -- </autoinc-2.9> + }) + +test:do_execsql_test( + "autoinc-2.10", + [[ + INSERT INTO t1 VALUES(125,456); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.10> + "t1", 125 + -- </autoinc-2.10> + }) + +test:do_execsql_test( + "autoinc-2.11", + [[ + INSERT INTO t1 VALUES(-1234567,-1); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.11> + "t1", 125 + -- </autoinc-2.11> + }) + +test:do_execsql_test( + "autoinc-2.12", + [[ + INSERT INTO t1 VALUES(234,5678); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.12> + "t1", 234 + -- </autoinc-2.12> + }) + +test:do_execsql_test( + "autoinc-2.13", + [[ + DELETE FROM t1; + INSERT INTO t1 VALUES(NULL,1); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.13> + "t1", 235 + -- </autoinc-2.13> + }) + +test:do_execsql_test( + "autoinc-2.14", + [[ + SELECT * FROM t1; + ]], { + -- <autoinc-2.14> + 235, 1 + -- </autoinc-2.14> + }) + +-- # Manually change the autoincrement values in sqlite_sequence. +-- # +test:do_execsql_test( + "autoinc-2.20", + [[ + UPDATE sql_sequence SET seq=1234 WHERE name='t1'; + INSERT INTO t1 VALUES(NULL,2); + SELECT * FROM t1; + ]], { + -- <autoinc-2.20> + 235, 1, 1235, 2 + -- </autoinc-2.20> + }) + +test:do_execsql_test( + "autoinc-2.21", + [[ + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.21> + "t1", 1235 + -- </autoinc-2.21> + }) + +test:do_execsql_test( + "autoinc-2.22", + [[ + UPDATE sql_sequence SET seq=NULL WHERE name='t1'; + INSERT INTO t1 VALUES(NULL,3); + SELECT * FROM t1; + ]], { + -- <autoinc-2.22> + 235, 1, 1235, 2, 1236, 3 + -- </autoinc-2.22> + }) + +test:do_execsql_test( + "autoinc-2.23", + [[ + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.23> + "t1", 1236 + -- </autoinc-2.23> + }) + +test:do_execsql_test( + "autoinc-2.24", + [[ + UPDATE sql_sequence SET seq='a-string' WHERE name='t1'; + INSERT INTO t1 VALUES(NULL,4); + SELECT * FROM t1; + ]], { + -- <autoinc-2.24> + 235, 1, 1235, 2, 1236, 3, 1237, 4 + -- </autoinc-2.24> + }) + +test:do_execsql_test( + "autoinc-2.25", + [[ + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.25> + "t1", 1237 + -- </autoinc-2.25> + }) + +test:do_execsql_test( + "autoinc-2.26", + [[ + DELETE FROM sql_sequence WHERE name='t1'; + INSERT INTO t1 VALUES(NULL,5); + SELECT * FROM t1; + ]], { + -- <autoinc-2.26> + 235, 1, 1235, 2, 1236, 3, 1237, 4, 1238, 5 + -- </autoinc-2.26> + }) + +test:do_execsql_test( + "autoinc-2.27", + [[ + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.27> + "t1", 1238 + -- </autoinc-2.27> + }) + +test:do_execsql_test( + "autoinc-2.28", + [[ + UPDATE sql_sequence SET seq='-12345678901234567890' + WHERE name='t1'; + INSERT INTO t1 VALUES(NULL,6); + SELECT * FROM t1; + ]], { + -- <autoinc-2.28> + 235, 1, 1235, 2, 1236, 3, 1237, 4, 1238, 5, 1239, 6 + -- </autoinc-2.28> + }) + +test:do_execsql_test( + "autoinc-2.29", + [[ + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.29> + "t1", 1239 + -- </autoinc-2.29> + }) + +-- # Test multi-row inserts +-- # +test:do_execsql_test( + "autoinc-2.50", + [[ + DELETE FROM t1 WHERE y>=3; + INSERT INTO t1 SELECT NULL, y+2 FROM t1; + SELECT * FROM t1; + ]], { + -- <autoinc-2.50> + 235, 1, 1235, 2, 1240, 3, 1241, 4 + -- </autoinc-2.50> + }) + +test:do_execsql_test( + "autoinc-2.51", + [[ + SELECT * FROM sql_sequence + ]], { + -- <autoinc-2.51> + "t1", 1241 + -- </autoinc-2.51> + }) + +-- ifcapable tempdb { +-- do_test autoinc-2.52 { +-- execsql { +-- CREATE TEMP TABLE t2 AS SELECT y FROM t1; +-- } +-- execsql { +-- INSERT INTO t1 SELECT NULL, y+4 FROM t2; +-- SELECT * FROM t1; +-- } +-- } {235 1 1235 2 1240 3 1241 4 1242 5 1243 6 1244 7 1245 8} +-- do_test autoinc-2.53 { +-- execsql { +-- SELECT * FROM sqlite_sequence +-- } +-- } {t1 1245} +-- do_test autoinc-2.54 { +-- execsql { +-- DELETE FROM t1; +-- INSERT INTO t1 SELECT NULL, y FROM t2; +-- SELECT * FROM t1; +-- } +-- } {1246 1 1247 2 1248 3 1249 4} +-- do_test autoinc-2.55 { +-- execsql { +-- SELECT * FROM sqlite_sequence +-- } +-- } {t1 1249} +-- } +-- # Create multiple AUTOINCREMENT tables. Make sure all sequences are +-- # tracked separately and do not interfere with one another. +-- # +test:do_test( + "autoinc-2.70", + function() + test:catchsql([[ + DROP TABLE t2; + ]]) + return test:execsql([[ + CREATE TABLE t2(d, e INTEGER PRIMARY KEY AUTOINCREMENT, f); + INSERT INTO t2(d) VALUES(1); + SELECT * FROM sql_sequence; + ]]) + end, { + -- <autoinc-2.70> + "t1", 1241, "t2", 1 + -- </autoinc-2.70> + }) + +test:do_execsql_test( + "autoinc-2.71", + [[ + INSERT INTO t2(d) VALUES(2); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.71> + "t1", 1241, "t2", 2 + -- </autoinc-2.71> + }) + +test:do_execsql_test( + "autoinc-2.72", + [[ + INSERT INTO t1(x) VALUES(10000); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.72> + "t1", 10000, "t2", 2 + -- </autoinc-2.72> + }) + +test:do_execsql_test( + "autoinc-2.73", + [[ + CREATE TABLE t3(g INTEGER PRIMARY KEY AUTOINCREMENT, h); + INSERT INTO t3(h) VALUES(1); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.73> + "t1", 10000, "t2", 2, "t3", 1 + -- </autoinc-2.73> + }) + +test:do_execsql_test( + "autoinc-2.74", + [[ + INSERT INTO t2(d,e) VALUES(3,100); + SELECT * FROM sql_sequence; + ]], { + -- <autoinc-2.74> + "t1", 10000, "t2", 100, "t3", 1 + -- </autoinc-2.74> + }) + +-- When a table with an AUTOINCREMENT is deleted, the corresponding entry +-- in the SQLITE_SEQUENCE table should also be deleted. But the SQLITE_SEQUENCE +-- table itself should remain behind. +-- +test:do_execsql_test( + "autoinc-3.1", + [[ + SELECT name FROM sql_sequence + ]], { + -- <autoinc-3.1> + "t1", "t2", "t3" + -- </autoinc-3.1> + }) + +test:do_execsql_test( + "autoinc-3.2", + [[ + DROP TABLE t1; + SELECT name FROM sql_sequence; + ]], { + -- <autoinc-3.2> + "t2", "t3" + -- </autoinc-3.2> + }) + +test:do_execsql_test( + "autoinc-3.3", + [[ + DROP TABLE t3; + SELECT name FROM sql_sequence; + ]], { + -- <autoinc-3.3> + "t2" + -- </autoinc-3.3> + }) + +test:do_execsql_test( + "autoinc-3.4", + [[ + DROP TABLE t2; + SELECT name FROM sql_sequence; + ]], { + -- <autoinc-3.4> + + -- </autoinc-3.4> + }) + +-- AUTOINCREMENT on TEMP tables. +-- +-- Tarantool: TEMP tables are not supported yet. To be uncommented. #2119 +-- test:do_execsql_test( +-- "autoinc-4.1", +-- [[ +-- SELECT 1, name FROM sqlite_master WHERE type='table'; +-- SELECT 2, name FROM sqlite_temp_master WHERE type='table'; +-- ]], { +-- -- <autoinc-4.1> +-- 1, "sqlite_sequence" +-- -- </autoinc-4.1> +-- }) + +-- test:do_execsql_test( +-- "autoinc-4.2", +-- [[ +-- CREATE TABLE t1(x INTEGER PRIMARY KEY AUTOINCREMENT, y); +-- CREATE TEMP TABLE t3(a INTEGER PRIMARY KEY AUTOINCREMENT, b); +-- SELECT 1, name FROM sqlite_master WHERE type='table'; +-- SELECT 2, name FROM sqlite_temp_master WHERE type='table'; +-- ]], { +-- -- <autoinc-4.2> +-- 1, "sqlite_sequence", 1, "t1", 2, "t3", 2, "sqlite_sequence" +-- -- </autoinc-4.2> +-- }) + +-- test:do_execsql_test( +-- "autoinc-4.3", +-- [[ +-- SELECT 1, * FROM main.sqlite_sequence; +-- SELECT 2, * FROM temp.sqlite_sequence; +-- ]], { +-- -- <autoinc-4.3> + +-- -- </autoinc-4.3> +-- }) + +-- test:do_execsql_test( +-- "autoinc-4.4", +-- [[ +-- INSERT INTO t1 VALUES(10,1); +-- INSERT INTO t3 VALUES(20,2); +-- INSERT INTO t1 VALUES(NULL,3); +-- INSERT INTO t3 VALUES(NULL,4); +-- ]], { +-- -- <autoinc-4.4> + +-- -- </autoinc-4.4> +-- }) + +-- test:do_execsql_test( +-- "autoinc-4.4.1", +-- [[ +-- SELECT * FROM t1 UNION ALL SELECT * FROM t3; +-- ]], { +-- -- <autoinc-4.4.1> +-- 10, 1, 11, 3, 20, 2, 21, 4 +-- -- </autoinc-4.4.1> +-- }) + + + +-- -- ifcapable compound +-- test:do_execsql_test( +-- "autoinc-4.5", +-- [[ +-- SELECT 1, * FROM main.sqlite_sequence; +-- SELECT 2, * FROM temp.sqlite_sequence; +-- ]], { +-- -- <autoinc-4.5> +-- 1, "t1", 11, 2, "t3", 21 +-- -- </autoinc-4.5> +-- }) + +-- test:do_execsql_test( +-- "autoinc-4.6", +-- [[ +-- INSERT INTO t1 SELECT * FROM t3; +-- SELECT 1, * FROM main.sqlite_sequence; +-- SELECT 2, * FROM temp.sqlite_sequence; +-- ]], { +-- -- <autoinc-4.6> +-- 1, "t1", 21, 2, "t3", 21 +-- -- </autoinc-4.6> +-- }) + +-- test:do_execsql_test( +-- "autoinc-4.7", +-- [[ +-- INSERT INTO t3 SELECT x+100, y FROM t1; +-- SELECT 1, * FROM main.sqlite_sequence; +-- SELECT 2, * FROM temp.sqlite_sequence; +-- ]], { +-- -- <autoinc-4.7> +-- 1, "t1", 21, 2, "t3", 121 +-- -- </autoinc-4.7> +-- }) + +-- test:do_execsql_test( +-- "autoinc-4.8", +-- [[ +-- DROP TABLE t3; +-- SELECT 1, * FROM main.sqlite_sequence; +-- SELECT 2, * FROM temp.sqlite_sequence; +-- ]], { +-- -- <autoinc-4.8> +-- 1, "t1", 21 +-- -- </autoinc-4.8> +-- }) + +-- test:do_execsql_test( +-- "autoinc-4.9", +-- [[ +-- CREATE TEMP TABLE t2(p INTEGER PRIMARY KEY AUTOINCREMENT, q); +-- INSERT INTO t2 SELECT * FROM t1; +-- DROP TABLE t1; +-- SELECT 1, * FROM main.sqlite_sequence; +-- SELECT 2, * FROM temp.sqlite_sequence; +-- ]], { +-- -- <autoinc-4.9> +-- 2, "t2", 21 +-- -- </autoinc-4.9> +-- }) + +-- test:do_execsql_test( +-- "autoinc-4.10", +-- [[ +-- DROP TABLE t2; +-- SELECT 1, * FROM main.sqlite_sequence; +-- SELECT 2, * FROM temp.sqlite_sequence; +-- ]], { +-- -- <autoinc-4.10> + +-- -- </autoinc-4.10> +-- }) + + + +-- Requirement REQ00310: Make sure an insert fails if the sequence is +-- already at its maximum value. +-- +test:do_execsql_test( + "autoinc-6.1", + [[ + CREATE TABLE t6(v INTEGER PRIMARY KEY AUTOINCREMENT, w); + INSERT INTO t6 VALUES(9223372036854775808,1); + INSERT INTO t6 VALUES(NULL,1); + -- SELECT seq FROM sql_sequence WHERE name='t6'; + ]], { + -- <autoinc-6.1> + -- 9223372036854775807 + -- </autoinc-6.1> + }) + +test:do_catchsql_test( + "autoinc-6.2", + [[ + INSERT INTO t6 VALUES(NULL,1); + ]], { + -- <autoinc-6.2> + 1, "UNIQUE constraint failed: t6.v" + -- </autoinc-6.2> + }) + +-- Allow the AUTOINCREMENT keyword inside the parentheses +-- on a separate PRIMARY KEY designation. +-- +test:do_execsql_test( + "autoinc-7.1", + [[ + CREATE TABLE t7(x INTEGER, y REAL, PRIMARY KEY(x AUTOINCREMENT)); + INSERT INTO t7(y) VALUES(123); + INSERT INTO t7(y) VALUES(234); + DELETE FROM t7; + INSERT INTO t7(y) VALUES(345); + SELECT * FROM t7; + ]], { + -- <autoinc-7.1> + 3, 345.0 + -- </autoinc-7.1> + }) + +-- Test that if the AUTOINCREMENT is applied to a non integer primary key +-- the error message is sensible. +test:do_catchsql_test( + "autoinc-7.2", + [[ + CREATE TABLE t8(x TEXT PRIMARY KEY AUTOINCREMENT); + ]], { + -- <autoinc-7.2> + 1, "AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY" + -- </autoinc-7.2> + }) + +-- Ticket #3148 +-- Make sure the sqlite_sequence table is not damaged when doing +-- an empty insert - an INSERT INTO ... SELECT ... where the SELECT +-- clause returns an empty set. +-- +test:do_test( + "autoinc-9.1", + function() + return test:execsql([[ + CREATE TABLE t2(x INTEGER PRIMARY KEY AUTOINCREMENT, y); + INSERT INTO t2 VALUES(NULL, 1); + CREATE TABLE t3(a INTEGER PRIMARY KEY AUTOINCREMENT, b); + INSERT INTO t3 SELECT * FROM t2 WHERE y>1; + + SELECT * FROM sql_sequence WHERE name='t3'; + ]]) + end, { + -- <autoinc-9.1> + "t3", 0 + -- </autoinc-9.1> + }) + +test:catchsql(" pragma recursive_triggers = off ") +-- Ticket #3928. Make sure that triggers to not make extra slots in +-- the SQLITE_SEQUENCE table. +-- +test:do_test( + "autoinc-3928.1", + function() + return test:execsql([[ + CREATE TABLE t3928(a INTEGER PRIMARY KEY AUTOINCREMENT, b); + CREATE TRIGGER t3928r1 BEFORE INSERT ON t3928 BEGIN + INSERT INTO t3928(b) VALUES('before1'); + INSERT INTO t3928(b) VALUES('before2'); + END; + CREATE TRIGGER t3928r2 AFTER INSERT ON t3928 BEGIN + INSERT INTO t3928(b) VALUES('after1'); + INSERT INTO t3928(b) VALUES('after2'); + END; + INSERT INTO t3928(b) VALUES('test'); + SELECT * FROM t3928 ORDER BY a; + ]]) + end, { + -- <autoinc-3928.1> + 1, "before1", 2, "after1", 3, "after2", 4, "before2", 5, "after1", 6, "after2", 7, "test", 8, "before1", 9, "before2", 10, "after1", 11, "before1", 12, "before2", 13, "after2" + -- </autoinc-3928.1> + }) + +test:do_test( + "autoinc-3928.2", + function() + return test:execsql([[ + SELECT * FROM sql_sequence WHERE name='t3928' + ]]) + end, { + -- <autoinc-3928.2> + "t3928", 13 + -- </autoinc-3928.2> + }) + +test:do_test( + "autoinc-3928.3", + function() + return test:execsql([[ + DROP TRIGGER t3928r1; + DROP TRIGGER t3928r2; + CREATE TRIGGER t3928r3 BEFORE UPDATE ON t3928 + WHEN typeof(new.b)=='integer' BEGIN + INSERT INTO t3928(b) VALUES('before-int-' || new.b); + END; + CREATE TRIGGER t3928r4 AFTER UPDATE ON t3928 + WHEN typeof(new.b)=='integer' BEGIN + INSERT INTO t3928(b) VALUES('after-int-' || new.b); + END; + DELETE FROM t3928 WHERE a!=1; + UPDATE t3928 SET b=456 WHERE a=1; + SELECT * FROM t3928 ORDER BY a; + ]]) + end, { + -- <autoinc-3928.3> + 1, 456, 14, "before-int-456", 15, "after-int-456" + -- </autoinc-3928.3> + }) + +test:do_test( + "autoinc-3928.4", + function() + return test:execsql([[ + SELECT * FROM sql_sequence WHERE name='t3928' + ]]) + end, { + -- <autoinc-3928.4> + "t3928", 15 + -- </autoinc-3928.4> + }) + +test:do_test( + "autoinc-3928.5", + function() + return test:execsql([[ + CREATE TABLE t3928b(x INTEGER PRIMARY KEY); + INSERT INTO t3928b VALUES(100); + INSERT INTO t3928b VALUES(200); + INSERT INTO t3928b VALUES(300); + DELETE FROM t3928; + CREATE TABLE t3928c(y INTEGER PRIMARY KEY AUTOINCREMENT, z); + CREATE TRIGGER t3928br1 BEFORE DELETE ON t3928b BEGIN + INSERT INTO t3928(b) VALUES('before-del-'||old.x); + INSERT INTO t3928c(z) VALUES('before-del-'||old.x); + END; + CREATE TRIGGER t3928br2 AFTER DELETE ON t3928b BEGIN + INSERT INTO t3928(b) VALUES('after-del-'||old.x); + INSERT INTO t3928c(z) VALUES('after-del-'||old.x); + END; + DELETE FROM t3928b; + SELECT * FROM t3928 ORDER BY a; + ]]) + end, { + -- <autoinc-3928.5> + 16, "before-del-100", 17, "after-del-100", 18, "before-del-200", 19, "after-del-200", 20, "before-del-300", 21, "after-del-300" + -- </autoinc-3928.5> + }) + +test:do_test( + "autoinc-3928.6", + function() + return test:execsql([[ + SELECT * FROM t3928c ORDER BY y; + ]]) + end, { + -- <autoinc-3928.6> + 1, "before-del-100", 2, "after-del-100", 3, "before-del-200", 4, "after-del-200", 5, "before-del-300", 6, "after-del-300" + -- </autoinc-3928.6> + }) + +test:do_test( + "autoinc-3928.7", + function() + return test:execsql([[ + SELECT * FROM sql_sequence WHERE name LIKE 't3928%' ORDER BY name; + ]]) + end, { + -- <autoinc-3928.7> + "t3928", 21, "t3928c", 6 + -- </autoinc-3928.7> + }) + +-- Ticket [a696379c1f0886615541a48b35bd8181a80e88f8] +test:do_test( + "autoinc-a69637.1", + function() + return test:execsql([[ + CREATE TABLE ta69637_1(x INTEGER PRIMARY KEY AUTOINCREMENT, y); + CREATE TABLE ta69637_2(z INTEGER PRIMARY KEY); + CREATE TRIGGER ra69637_1 AFTER INSERT ON ta69637_2 BEGIN + INSERT INTO ta69637_1(y) VALUES(new.z+1); + END; + INSERT INTO ta69637_2 VALUES(123); + SELECT * FROM ta69637_1; + ]]) + end, { + -- <autoinc-a69637.1> + 1, 124 + -- </autoinc-a69637.1> + }) + +test:do_test( + "autoinc-a69637.2", + function() + return test:execsql([[ + CREATE VIEW va69637_2 AS SELECT * FROM ta69637_2; + CREATE TRIGGER ra69637_2 INSTEAD OF INSERT ON va69637_2 BEGIN + INSERT INTO ta69637_1(y) VALUES(new.z+10000); + END; + INSERT INTO va69637_2 VALUES(123); + SELECT * FROM ta69637_1; + ]]) + end, { + -- <autoinc-a69637.2> + 1, 124, 2, 10123 + -- </autoinc-a69637.2> + }) + +test:finish_test() -- GitLab