aboutsummaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorlarrybr <larrybr@noemail.net>2023-04-28 21:25:03 +0000
committerlarrybr <larrybr@noemail.net>2023-04-28 21:25:03 +0000
commitb0d46fcd72feb143d1ad5d585df943c7a4bc5ee3 (patch)
tree23a51f135fa3c0334083314d56a8fe592795107c /ext
parent93b4c3beb8f005e1c0ce6f27b1675e4b2f24acc4 (diff)
downloadsqlite-b0d46fcd72feb143d1ad5d585df943c7a4bc5ee3.tar.gz
sqlite-b0d46fcd72feb143d1ad5d585df943c7a4bc5ee3.zip
Revise generate_series() extension (in CLI) to address overflow reported in [forum:754e2d4db2a5|forum post #754e2d4db2a5] and to make behavior better match the like-named PostgreSQL function.
FossilOrigin-Name: beeea3e1b010dace9789f27172462b912819d0f8142a67e3e1e7335211e0e9a8
Diffstat (limited to 'ext')
-rw-r--r--ext/misc/series.c182
1 files changed, 135 insertions, 47 deletions
diff --git a/ext/misc/series.c b/ext/misc/series.c
index 3941d96c4..f484cba2d 100644
--- a/ext/misc/series.c
+++ b/ext/misc/series.c
@@ -1,5 +1,5 @@
/*
-** 2015-08-18
+** 2015-08-18, 2023-04-28
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
@@ -12,7 +12,19 @@
**
** This file demonstrates how to create a table-valued-function using
** a virtual table. This demo implements the generate_series() function
-** which gives similar results to the eponymous function in PostgreSQL.
+** which gives the same results as the eponymous function in PostgreSQL,
+** within the limitation that its arguments are signed 64-bit integers.
+**
+** Considering its equivalents to generate_series(start,stop,step): A
+** value V[n] sequence is produced for integer n ascending from 0 where
+** ( V[n] == start + n * step && sgn(V[n] - stop) * sgn(step) >= 0 )
+** for each produced value (independent of production time ordering.)
+**
+** All parameters must be either integer or convertable to integer.
+** The start parameter is required.
+** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff)
+** The step parameter defaults to 1 and 0 is treated as 1.
+**
** Examples:
**
** SELECT * FROM generate_series(0,100,5);
@@ -28,6 +40,14 @@
**
** Integers 20 through 29.
**
+** SELECT * FROM generate_series(0,-100,-5);
+**
+** Integers 0 -5 -10 ... -100.
+**
+** SELECT * FROM generate_series(0,-1);
+**
+** Empty sequence.
+**
** HOW IT WORKS
**
** The generate_series "function" is really a virtual table with the
@@ -40,6 +60,9 @@
** step HIDDEN
** );
**
+** The virtual table also has a rowid, logically equivalent to n+1 where
+** "n" is the ascending integer in the aforesaid production definition.
+**
** Function arguments in queries against this virtual table are translated
** into equality constraints against successive hidden columns. In other
** words, the following pairs of queries are equivalent to each other:
@@ -72,9 +95,93 @@
SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
+#include <limits.h>
#ifndef SQLITE_OMIT_VIRTUALTABLE
+/*
+** Return that member of a generate_series(...) sequence whose 0-based
+** index is ix. The 0th member is given by smBase. The sequence members
+** progress per ix increment by smStep.
+*/
+static sqlite3_int64 genSeqMember(sqlite3_int64 smBase,
+ sqlite3_int64 smStep,
+ sqlite3_uint64 ix){
+ if( ix>=(sqlite3_uint64)LLONG_MAX ){
+ /* Get ix into signed i64 range. */
+ ix -= (sqlite3_uint64)LLONG_MAX;
+ smBase += LLONG_MAX * smStep;
+ }
+ return smBase + ((sqlite3_int64)ix)*smStep;
+}
+
+typedef unsigned char u8;
+
+typedef struct SequenceSpec {
+ sqlite3_int64 iBase; /* Starting value ("start") */
+ sqlite3_int64 iTerm; /* Given terminal value ("stop") */
+ sqlite3_int64 iStep; /* Increment ("step") */
+ sqlite3_uint64 uMaxRowidM1; /* maximum rowid minus 1 */
+ sqlite3_uint64 uRidCurrent; /* Current rowid-1 during generation */
+ sqlite3_int64 iValueCurrent; /* Current value during generation */
+ u8 isNotEOF; /* Sequence generation not exhausted */
+ u8 isReversing; /* Sequence is being reverse generated */
+} SequenceSpec;
+
+/*
+** Prepare a SequenceSpec for use in generating an integer series
+** given initialized iBase, iTerm and iStep values. Sequence is
+** initialized per given isReversing. Other members are computed.
+*/
+void setupSequence( SequenceSpec *pss ){
+ pss->uMaxRowidM1 = 0;
+ pss->isNotEOF = 0;
+ if( pss->iTerm < pss->iBase ){
+ sqlite3_uint64 nuspan = (sqlite3_uint64)(pss->iBase-pss->iTerm);
+ if( pss->iStep<0 ){
+ pss->isNotEOF = 1;
+ if( nuspan==ULONG_MAX ){
+ pss->uMaxRowidM1 = ( pss->iStep>LLONG_MIN )? nuspan/-pss->iStep : 1;
+ }else if( pss->iStep>LLONG_MIN ){
+ pss->uMaxRowidM1 = nuspan/-pss->iStep;
+ }
+ }
+ }else if( pss->iTerm > pss->iBase ){
+ sqlite3_uint64 puspan = (sqlite3_uint64)(pss->iTerm-pss->iBase);
+ if( pss->iStep>0 ){
+ pss->isNotEOF = 1;
+ pss->uMaxRowidM1 = puspan/pss->iStep;
+ }
+ }
+ pss->uRidCurrent = (pss->isReversing)? pss->uMaxRowidM1 : 0;
+ pss->iValueCurrent = (pss->isReversing)
+ ? genSeqMember(pss->iBase, pss->iStep, pss->uMaxRowidM1)
+ : pss->iBase;
+}
+/*
+** Progress sequence generator to yield next value, if any.
+** Leave its state to either yield next value or be at EOF.
+** Return whether there is a next value, or 0 at EOF.
+*/
+int progressSequence( SequenceSpec *pss ){
+ if( !pss->isNotEOF ) return 0;
+ if( pss->isReversing ){
+ if( pss->uRidCurrent > 0 ){
+ pss->uRidCurrent--;
+ pss->iValueCurrent -= pss->iStep;
+ }else{
+ pss->isNotEOF = 0;
+ }
+ }else{
+ if( pss->uRidCurrent < pss->uMaxRowidM1 ){
+ pss->uRidCurrent++;
+ pss->iValueCurrent += pss->iStep;
+ }else{
+ pss->isNotEOF = 0;
+ }
+ }
+ return pss->isNotEOF;
+}
/* series_cursor is a subclass of sqlite3_vtab_cursor which will
** serve as the underlying representation of a cursor that scans
@@ -83,12 +190,7 @@ SQLITE_EXTENSION_INIT1
typedef struct series_cursor series_cursor;
struct series_cursor {
sqlite3_vtab_cursor base; /* Base class - must be first */
- int isDesc; /* True to count down rather than up */
- sqlite3_int64 iRowid; /* The rowid */
- sqlite3_int64 iValue; /* Current value ("value") */
- sqlite3_int64 mnValue; /* Mimimum value ("start") */
- sqlite3_int64 mxValue; /* Maximum value ("stop") */
- sqlite3_int64 iStep; /* Increment ("step") */
+ SequenceSpec ss; /* (this) Derived class data */
};
/*
@@ -170,12 +272,7 @@ static int seriesClose(sqlite3_vtab_cursor *cur){
*/
static int seriesNext(sqlite3_vtab_cursor *cur){
series_cursor *pCur = (series_cursor*)cur;
- if( pCur->isDesc ){
- pCur->iValue -= pCur->iStep;
- }else{
- pCur->iValue += pCur->iStep;
- }
- pCur->iRowid++;
+ progressSequence( & pCur->ss );
return SQLITE_OK;
}
@@ -191,10 +288,10 @@ static int seriesColumn(
series_cursor *pCur = (series_cursor*)cur;
sqlite3_int64 x = 0;
switch( i ){
- case SERIES_COLUMN_START: x = pCur->mnValue; break;
- case SERIES_COLUMN_STOP: x = pCur->mxValue; break;
- case SERIES_COLUMN_STEP: x = pCur->iStep; break;
- default: x = pCur->iValue; break;
+ case SERIES_COLUMN_START: x = pCur->ss.iBase; break;
+ case SERIES_COLUMN_STOP: x = pCur->ss.iTerm; break;
+ case SERIES_COLUMN_STEP: x = pCur->ss.iStep; break;
+ default: x = pCur->ss.iValueCurrent; break;
}
sqlite3_result_int64(ctx, x);
return SQLITE_OK;
@@ -207,7 +304,7 @@ static int seriesColumn(
*/
static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
series_cursor *pCur = (series_cursor*)cur;
- *pRowid = pCur->iRowid;
+ *pRowid = ((sqlite3_int64)pCur->ss.uRidCurrent + 1);
return SQLITE_OK;
}
@@ -217,14 +314,10 @@ static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
*/
static int seriesEof(sqlite3_vtab_cursor *cur){
series_cursor *pCur = (series_cursor*)cur;
- if( pCur->isDesc ){
- return pCur->iValue < pCur->mnValue;
- }else{
- return pCur->iValue > pCur->mxValue;
- }
+ return !pCur->ss.isNotEOF;
}
-/* True to cause run-time checking of the start=, stop=, and/or step=
+/* True to cause run-time checking of the start=, stop=, and/or step=
** parameters. The only reason to do this is for testing the
** constraint checking logic for virtual tables in the SQLite core.
*/
@@ -235,7 +328,7 @@ static int seriesEof(sqlite3_vtab_cursor *cur){
/*
** This method is called to "rewind" the series_cursor object back
** to the first row of output. This method is always called at least
-** once prior to any call to seriesColumn() or seriesRowid() or
+** once prior to any call to seriesColumn() or seriesRowid() or
** seriesEof().
**
** The query plan selected by seriesBestIndex is passed in the idxNum
@@ -255,7 +348,7 @@ static int seriesEof(sqlite3_vtab_cursor *cur){
** (so that seriesEof() will return true) if the table is empty.
*/
static int seriesFilter(
- sqlite3_vtab_cursor *pVtabCursor,
+ sqlite3_vtab_cursor *pVtabCursor,
int idxNum, const char *idxStrUnused,
int argc, sqlite3_value **argv
){
@@ -263,46 +356,41 @@ static int seriesFilter(
int i = 0;
(void)idxStrUnused;
if( idxNum & 1 ){
- pCur->mnValue = sqlite3_value_int64(argv[i++]);
+ pCur->ss.iBase = sqlite3_value_int64(argv[i++]);
}else{
- pCur->mnValue = 0;
+ pCur->ss.iBase = 0;
}
if( idxNum & 2 ){
- pCur->mxValue = sqlite3_value_int64(argv[i++]);
+ pCur->ss.iTerm = sqlite3_value_int64(argv[i++]);
}else{
- pCur->mxValue = 0xffffffff;
+ pCur->ss.iTerm = 0xffffffff;
}
if( idxNum & 4 ){
- pCur->iStep = sqlite3_value_int64(argv[i++]);
- if( pCur->iStep==0 ){
- pCur->iStep = 1;
- }else if( pCur->iStep<0 ){
- pCur->iStep = -pCur->iStep;
+ pCur->ss.iStep = sqlite3_value_int64(argv[i++]);
+ if( pCur->ss.iStep==0 ){
+ pCur->ss.iStep = 1;
+ }else if( pCur->ss.iStep<0 ){
if( (idxNum & 16)==0 ) idxNum |= 8;
}
}else{
- pCur->iStep = 1;
+ pCur->ss.iStep = 1;
}
for(i=0; i<argc; i++){
if( sqlite3_value_type(argv[i])==SQLITE_NULL ){
/* If any of the constraints have a NULL value, then return no rows.
** See ticket https://www.sqlite.org/src/info/fac496b61722daf2 */
- pCur->mnValue = 1;
- pCur->mxValue = 0;
+ pCur->ss.iBase = 1;
+ pCur->ss.iTerm = 0;
+ pCur->ss.iStep = 1;
break;
}
}
if( idxNum & 8 ){
- pCur->isDesc = 1;
- pCur->iValue = pCur->mxValue;
- if( pCur->iStep>0 ){
- pCur->iValue -= (pCur->mxValue - pCur->mnValue)%pCur->iStep;
- }
+ pCur->ss.isReversing = pCur->ss.iStep > 0;
}else{
- pCur->isDesc = 0;
- pCur->iValue = pCur->mnValue;
+ pCur->ss.isReversing = pCur->ss.iStep < 0;
}
- pCur->iRowid = 1;
+ setupSequence( &pCur->ss );
return SQLITE_OK;
}