Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -580,10 +580,11 @@ SHELL_OPT += -DSQLITE_ENABLE_EXPLAIN_COMMENTS SHELL_OPT += -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION SHELL_OPT += -DSQLITE_ENABLE_STMTVTAB SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB +SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1 FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_OSS_FUZZ FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000 FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c DBFUZZ_OPT = Index: Makefile.msc ================================================================== --- Makefile.msc +++ Makefile.msc @@ -1511,10 +1511,11 @@ # when the shell is not being dynamically linked. # !IF $(DYNAMIC_SHELL)==0 && $(FOR_WIN10)==0 SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_STMTVTAB SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_ENABLE_DBSTAT_VTAB +SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_ENABLE_OFFSET_SQL_FUNC !ENDIF # <> # Extra compiler options for various test tools. # Index: main.mk ================================================================== --- main.mk +++ main.mk @@ -507,10 +507,11 @@ SHELL_OPT += -DSQLITE_ENABLE_EXPLAIN_COMMENTS SHELL_OPT += -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION SHELL_OPT += -DSQLITE_ENABLE_STMTVTAB SHELL_OPT += -DSQLITE_ENABLE_DBPAGE_VTAB SHELL_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB +SHELL_OPT += -DSQLITE_ENABLE_OFFSET_SQL_FUNC FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1 FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5 FUZZCHECK_OPT += -DSQLITE_MAX_MEMORY=50000000 DBFUZZ_OPT = KV_OPT = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -4430,10 +4430,24 @@ assert( pCur->curIntKey ); getCellInfo(pCur); return pCur->info.nKey; } +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC +/* +** Return the offset into the database file for the start of the +** payload to which the cursor is pointing. +*/ +i64 sqlite3BtreeOffset(BtCursor *pCur){ + assert( cursorHoldsMutex(pCur) ); + assert( pCur->eState==CURSOR_VALID ); + getCellInfo(pCur); + return (i64)pCur->pBt->pageSize*((i64)pCur->pPage->pgno - 1) + + (i64)(pCur->info.pPayload - pCur->pPage->aData); +} +#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */ + /* ** Return the number of bytes of payload for the entry that pCur is ** currently pointing to. For table btrees, this will be the amount ** of data. For index btrees, this will be the size of the key. ** Index: src/btree.h ================================================================== --- src/btree.h +++ src/btree.h @@ -289,10 +289,13 @@ int sqlite3BtreeLast(BtCursor*, int *pRes); int sqlite3BtreeNext(BtCursor*, int flags); int sqlite3BtreeEof(BtCursor*); int sqlite3BtreePrevious(BtCursor*, int flags); i64 sqlite3BtreeIntegerKey(BtCursor*); +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC +i64 sqlite3BtreeOffset(BtCursor*); +#endif int sqlite3BtreePayload(BtCursor*, u32 offset, u32 amt, void*); const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); u32 sqlite3BtreePayloadSize(BtCursor*); char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*); Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -3868,13 +3868,25 @@ #endif if( pDef->funcFlags & SQLITE_FUNC_NEEDCOLL ){ if( !pColl ) pColl = db->pDfltColl; sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); } - sqlite3VdbeAddOp4(v, pParse->iSelfTab ? OP_PureFunc0 : OP_Function0, - constMask, r1, target, (char*)pDef, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, (u8)nFarg); +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + if( pDef->funcFlags & SQLITE_FUNC_OFFSET ){ + Expr *pArg = pFarg->a[0].pExpr; + if( pArg->op==TK_COLUMN ){ + sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + } + }else +#endif + { + sqlite3VdbeAddOp4(v, pParse->iSelfTab ? OP_PureFunc0 : OP_Function0, + constMask, r1, target, (char*)pDef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, (u8)nFarg); + } if( nFarg && constMask==0 ){ sqlite3ReleaseTempRange(pParse, r1, nFarg); } return target; } Index: src/func.c ================================================================== --- src/func.c +++ src/func.c @@ -1797,10 +1797,15 @@ FUNCTION2(likelihood, 2, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), FUNCTION2(likely, 1, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), #ifdef SQLITE_DEBUG FUNCTION2(affinity, 1, 0, 0, noopFunc, SQLITE_FUNC_AFFINITY), #endif +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + FUNCTION2(sqlite_unsupported_offset, + 1, 0, 0, noopFunc, SQLITE_FUNC_OFFSET| + SQLITE_FUNC_TYPEOF), +#endif FUNCTION(ltrim, 1, 1, 0, trimFunc ), FUNCTION(ltrim, 2, 1, 0, trimFunc ), FUNCTION(rtrim, 1, 2, 0, trimFunc ), FUNCTION(rtrim, 2, 2, 0, trimFunc ), FUNCTION(trim, 1, 3, 0, trimFunc ), Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -1627,10 +1627,11 @@ #define SQLITE_FUNC_CONSTANT 0x0800 /* Constant inputs give a constant output */ #define SQLITE_FUNC_MINMAX 0x1000 /* True for min() and max() aggregates */ #define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a ** single query - might change over time */ #define SQLITE_FUNC_AFFINITY 0x4000 /* Built-in affinity() function */ +#define SQLITE_FUNC_OFFSET 0x8000 /* Built-in sqlite_offset() function */ /* ** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are ** used to create the initializers for the FuncDef structures. ** Index: src/test_config.c ================================================================== --- src/test_config.c +++ src/test_config.c @@ -157,10 +157,16 @@ #ifdef SQLITE_ENABLE_MEMSYS5 Tcl_SetVar2(interp, "sqlite_options", "mem5", "1", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "mem5", "0", TCL_GLOBAL_ONLY); #endif + +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + Tcl_SetVar2(interp, "sqlite_options", "offset_sql_func","1",TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "offset_sql_func","0",TCL_GLOBAL_ONLY); +#endif #ifdef SQLITE_ENABLE_PREUPDATE_HOOK Tcl_SetVar2(interp, "sqlite_options", "preupdate", "1", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "preupdate", "0", TCL_GLOBAL_ONLY); Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -2347,10 +2347,40 @@ goto jump_to_p2; } break; } +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC +/* Opcode: Offset P1 P2 P3 * * +** Synopsis: r[P3] = sqlite_offset(P1) +** +** Store in register r[P3] the byte offset into the database file that is the +** start of the payload for the record at which that cursor P1 is currently +** pointing. +** +** P2 is the column number for the argument to the sqlite_offset() function. +** This opcode does not use P2 itself, but the P2 value is used by the +** code generator. The P1, P2, and P3 operands to this opcode are the +** as as for OP_Column. +** +** This opcode is only available if SQLite is compiled with the +** -DSQLITE_ENABLE_OFFSET_SQL_FUNC option. +*/ +case OP_Offset: { /* out3 */ + VdbeCursor *pC; /* The VDBE cursor */ + assert( pOp->p1>=0 && pOp->p1nCursor ); + pC = p->apCsr[pOp->p1]; + pOut = &p->aMem[pOp->p3]; + if( pC==0 || pC->eCurType!=CURTYPE_BTREE ){ + sqlite3VdbeMemSetNull(pOut); + }else{ + sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor)); + } + break; +} +#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */ + /* Opcode: Column P1 P2 P3 P4 P5 ** Synopsis: r[P3]=PX ** ** Interpret the data that cursor P1 points to as a structure built using ** the MakeRecord instruction. (See the MakeRecord opcode for additional Index: src/where.c ================================================================== --- src/where.c +++ src/where.c @@ -5144,11 +5144,15 @@ last = sqlite3VdbeCurrentAddr(v); k = pLevel->addrBody; pOp = sqlite3VdbeGetOp(v, k); for(; kp1!=pLevel->iTabCur ) continue; - if( pOp->opcode==OP_Column ){ + if( pOp->opcode==OP_Column +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + || pOp->opcode==OP_Offset +#endif + ){ int x = pOp->p2; assert( pIdx->pTable==pTab ); if( !HasRowid(pTab) ){ Index *pPk = sqlite3PrimaryKeyIndex(pTab); x = pPk->aiColumn[x]; ADDED test/func6.test Index: test/func6.test ================================================================== --- /dev/null +++ test/func6.test @@ -0,0 +1,86 @@ +# 2017-12-16 +# +# 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. +# +#************************************************************************* +# +# Test cases for the sqlite_unsupported_offset() function. +# +# Some of the tests in this file depend on the exact placement of content +# within b-tree pages. Such placement is at the implementations discretion, +# and so it is possible for results to change from one release to the next. +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl +ifcapable !offset_sql_func { + finish_test + return +} + +do_execsql_test func6-100 { + PRAGMA page_size=4096; + PRAGMA auto_vacuum=NONE; + CREATE TABLE t1(a,b,c,d); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100) + INSERT INTO t1(a,b,c,d) SELECT printf('abc%03x',x), x, 1000-x, NULL FROM c; + CREATE INDEX t1a ON t1(a); + CREATE INDEX t1bc ON t1(b,c); + CREATE TABLE t2(x TEXT PRIMARY KEY, y) WITHOUT ROWID; + INSERT INTO t2(x,y) SELECT a, b FROM t1; +} +do_execsql_test func6-110 { + SELECT a, sqlite_unsupported_offset(d)/4096 + 1, + sqlite_unsupported_offset(d)%4096 FROM t1 + ORDER BY rowid LIMIT 2; +} {abc001 2 4084 abc002 2 4069} +do_execsql_test func6-120 { + SELECT a, typeof(sqlite_unsupported_offset(+a)) FROM t1 + ORDER BY rowid LIMIT 2; +} {abc001 null abc002 null} +do_execsql_test func6-130 { + SELECT a, sqlite_unsupported_offset(a)/4096+1, + sqlite_unsupported_offset(a)%4096 + FROM t1 + ORDER BY a LIMIT 2; +} {abc001 3 4087 abc002 3 4076} +do_execsql_test func6-140 { + SELECT a, sqlite_unsupported_offset(d)/4096+1, + sqlite_unsupported_offset(d)%4096 + FROM t1 + ORDER BY a LIMIT 2; +} {abc001 2 4084 abc002 2 4069} +do_execsql_test func6-150 { + SELECT a, + sqlite_unsupported_offset(a)/4096+1, + sqlite_unsupported_offset(a)%4096, + sqlite_unsupported_offset(d)/4096+1, + sqlite_unsupported_offset(d)%4096 + FROM t1 + ORDER BY a LIMIT 2; +} {abc001 3 4087 2 4084 abc002 3 4076 2 4069} +do_execsql_test func6-160 { + SELECT b, + sqlite_unsupported_offset(b)/4096+1, + sqlite_unsupported_offset(b)%4096, + sqlite_unsupported_offset(c)/4096+1, + sqlite_unsupported_offset(c)%4096, + sqlite_unsupported_offset(d)/4096+1, + sqlite_unsupported_offset(d)%4096 + FROM t1 + ORDER BY b LIMIT 2; +} {1 4 4090 4 4090 2 4084 2 4 4081 4 4081 2 4069} + + +do_execsql_test func6-200 { + SELECT y, sqlite_unsupported_offset(y)/4096+1, + sqlite_unsupported_offset(y)%4096 + FROM t2 + ORDER BY x LIMIT 2; +} {1 5 4087 2 5 4076} + +finish_test