From 600b1b2f0185a8b671a05624779039773fb62764 Mon Sep 17 00:00:00 2001 From: drh Date: Mon, 5 Jun 2000 21:39:48 +0000 Subject: [PATCH] added Agg opcodes to the vdbe (CVS 54) FossilOrigin-Name: e9ed5d2a3639161fb58856275ba9495f21d366bf --- manifest | 16 +-- manifest.uuid | 2 +- src/vdbe.c | 290 ++++++++++++++++++++++++++++++++++++++++++-- src/vdbe.h | 85 +++++++------ test/subselect.test | 10 +- 5 files changed, 343 insertions(+), 60 deletions(-) diff --git a/manifest b/manifest index 82b8b31a1a..ff6a910d3c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C :-)\s(CVS\s53) -D 2000-06-05T18:56:43 +C added\sAgg\sopcodes\sto\sthe\svdbe\s(CVS\s54) +D 2000-06-05T21:39:49 F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4 F Makefile.in 17ba1ccf8d2d40c627796bba8f72952365d6d644 F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958 @@ -22,8 +22,8 @@ F src/tclsqlite.c 9f358618ae803bedf4fb96da5154fd45023bc1f7 F src/tokenize.c 15c229fee77325334c6814652e429b0930eba6c1 F src/update.c 3f05d5082fd2c34f15d1e4a4db17355ad8807a78 F src/util.c 33f9baa01e45394ef0cf85361a0e872987884315 -F src/vdbe.c b8b78d0eaae9aa68e96b9d37da7d4571905396ca -F src/vdbe.h 5fd02cb52af0efa0165dd67b6e0036b853e1620f +F src/vdbe.c e14cf771e4f487feb433fc6c0c6bd9536167a953 +F src/vdbe.h 7b3cd6b122b72ce8d2f5ab40069748d32f1d9df0 F src/where.c 6b840a726b06b5122f112e3bc3c142a230af6251 F test/all.test 0950c135cab7e60c07bd745ccfad1476211e5bd7 F test/copy.test 73c3783535db538c8ebd8fffb931376864fc3226 @@ -33,7 +33,7 @@ F test/index.test 9f99dca2d904b8de330863a978587f136e2df65a F test/insert.test b4c186ffa4b97a231643726f3bcee29815b24eaf F test/select1.test a0b00df77e85adff75c338e487718c5d31f69e3a F test/select2.test 3cd3c0f9d67e98b1b54af5853679b4a111224410 -F test/subselect.test 9bba3573f3364b4ae4b8c48bbf51d782e806ce11 +F test/subselect.test bf8b251a92fb091973c1c469ce499dc9648a41d5 F test/table.test 85d6f410d127ec508c6640f02d7c40d218414e81 F test/tester.tcl 44690d463c1dc83a4c76ccde07cc146a988600f6 F test/update.test 69459302ea75cafac1479e60b0e36efb88123c0e @@ -48,7 +48,7 @@ F www/c_interface.tcl 8867d76ddd416d2fbd41e4cb3de8efa9cef105a5 F www/changes.tcl 567cc6066d87460bdedff8e5bbc20f41ddaadf77 F www/index.tcl f8189a7898f6d06307c34047b9d7e00860026e44 F www/sqlite.tcl 2f933ce18cffd34a0a020a82435ab937137970fd -P c02268bdf4c28edc2542ce0ca1ba24fd6b5058fa -R 048dcd3eb7b277c896b652db05acc39f +P 3dbfa558ef01ac9249a3f40da49bc71cc7b040bc +R 2c42d01480de541004757564bdb21603 U drh -Z 435b2a37a755d776ddffa2796ea2a6cd +Z 3c51c450bc15ba9b865ce0cc0b812208 diff --git a/manifest.uuid b/manifest.uuid index 63e8a6ccb1..36ad03c666 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3dbfa558ef01ac9249a3f40da49bc71cc7b040bc \ No newline at end of file +e9ed5d2a3639161fb58856275ba9495f21d366bf \ No newline at end of file diff --git a/src/vdbe.c b/src/vdbe.c index 1b836bb8fb..04e8be71d2 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -41,7 +41,7 @@ ** But other routines are also provided to help in building up ** a program instruction by instruction. ** -** $Id: vdbe.c,v 1.16 2000/06/05 18:54:47 drh Exp $ +** $Id: vdbe.c,v 1.17 2000/06/05 21:39:49 drh Exp $ */ #include "sqliteInt.h" #include @@ -106,6 +106,30 @@ struct Mem { }; typedef struct Mem Mem; +/* +** An Agg structure describes and Aggregator. Each Agg consists of +** zero or more Aggregator elements (AggElem). Each AggElem contains +** a key and one or more values. The values are used in processing +** aggregate functions in a SELECT. The key is used to implement +** the GROUP BY clause of a select. +*/ +typedef struct Agg Agg; +typedef struct AggElem AggElem; +struct Agg { + int nMem; /* Number of values stored in each AggElem */ + AggElem *pCurrent; /* The AggElem currently in focus */ + int nElem; /* The number of AggElems */ + int nHash; /* Number of slots in apHash[] */ + AggElem **apHash; /* A hash table for looking up AggElems by zKey */ + AggElem *pFirst; /* A list of all AggElems */ +}; +struct AggElem { + char *zKey; /* The key to this AggElem */ + AggElem *pHash; /* Next AggElem with the same hash on zKey */ + AggElem *pNext; /* Next AggElem in a list of them all */ + Mem aMem[1]; /* The values for this AggElem */ +}; + /* ** Allowed values for Stack.flags */ @@ -145,6 +169,7 @@ struct Vdbe { int nLineAlloc; /* Number of spaces allocated for zLine */ int nMem; /* Number of memory locations currently allocated */ Mem *aMem; /* The memory locations */ + Agg agg; /* Aggregate information */ }; /* @@ -327,6 +352,89 @@ int sqliteVdbeMakeLabel(Vdbe *p){ return -1-i; } +/* +** Reset an Agg structure. Delete all its contents. +*/ +static void AggReset(Agg *p){ + int i; + while( p->pFirst ){ + AggElem *pElem = p->pFirst; + p->pFirst = pElem->pNext; + for(i=0; inMem; i++){ + if( pElem->aMem[i].s.flags & STK_Dyn ){ + sqliteFree(pElem->aMem[i].z); + } + } + sqliteFree(pElem); + } + sqliteFree(p->apHash); + memset(p, 0, sizeof(*p)); +} + +/* +** Add the given AggElem to the hash table +*/ +static void AggEnhash(Agg *p, AggElem *pElem){ + int h = sqliteHashNoCase(pElem->zKey, 0) % p->nHash; + pElem->pHash = p->apHash[h]; + p->apHash[h] = pElem; +} + +/* +** Change the size of the hash table to the amount given. +*/ +static void AggRehash(Agg *p, int nHash){ + int size; + AggElem *pElem; + if( p->nHash==nHash ) return; + size = nHash * sizeof(AggElem*); + p->apHash = sqliteRealloc(p->apHash, size ); + memset(p->apHash, 0, size); + p->nHash = nHash; + for(pElem=p->pFirst; pElem; pElem=pElem->pNext){ + AggEnhash(p, pElem); + } +} + +/* +** Insert a new element and make it the current element. +** +** Return 0 on success and 1 if memory is exhausted. +*/ +static int AggInsert(Agg *p, char *zKey){ + AggElem *pElem; + if( p->nHash < p->nElem*2 ){ + AggRehash(p, p->nElem*2 + 103); + } + if( p->nHash==0 ) return 1; + pElem = sqliteMalloc( sizeof(AggElem) + strlen(zKey) + 1 + + (p->nMem-1)*sizeof(pElem->aMem[0]) ); + if( pElem==0 ) return 1; + pElem->zKey = (char*)&pElem->aMem[p->nMem]; + strcpy(pElem->zKey, zKey); + AggEnhash(p, pElem); + pElem->pNext = p->pFirst; + p->pFirst = pElem; + p->nElem++; + p->pCurrent = pElem; + return 0; +} + +/* +** Get the AggElem currently in focus +*/ +#define AggInFocus(P) ((P).pCurrent ? (P).pCurrent : _AggInFocus(&(P))) +static AggElem *_AggInFocus(Agg *p){ + AggElem *pFocus = p->pFirst; + if( pFocus ){ + p->pCurrent = pFocus; + }else{ + AggInsert(p,""); + pFocus = p->pCurrent; + } + return pFocus; +} + /* ** Convert the given stack entity into a string if it isn't one ** already. Return non-zero if we run out of memory. @@ -519,6 +627,7 @@ static void Cleanup(Vdbe *p){ p->zLine = 0; } p->nLineAlloc = 0; + AggReset(&p->agg); } /* @@ -561,16 +670,17 @@ static char *zOpName[] = { 0, "SortOpen", "SortPut", "SortMakeRec", "SortMakeKey", "Sort", "SortNext", "SortKey", "SortCallback", "SortClose", "FileOpen", "FileRead", "FileField", - "FileClose", "MakeRecord", "MakeKey", "Goto", - "If", "Halt", "ColumnCount", "ColumnName", - "Callback", "Integer", "String", "Null", - "Pop", "Dup", "Pull", "Add", - "AddImm", "Subtract", "Multiply", "Divide", - "Min", "Max", "Like", "Glob", - "Eq", "Ne", "Lt", "Le", - "Gt", "Ge", "IsNull", "NotNull", - "Negative", "And", "Or", "Not", - "Concat", "Noop", + "FileClose", "AggReset", "AggFocus", "AggIncr", + "AggNext", "AggSet", "AggGet", "MakeRecord", + "MakeKey", "Goto", "If", "Halt", + "ColumnCount", "ColumnName", "Callback", "Integer", + "String", "Null", "Pop", "Dup", + "Pull", "Add", "AddImm", "Subtract", + "Multiply", "Divide", "Min", "Max", + "Like", "Glob", "Eq", "Ne", + "Lt", "Le", "Gt", "Ge", + "IsNull", "NotNull", "Negative", "And", + "Or", "Not", "Concat", "Noop", }; /* @@ -2481,6 +2591,164 @@ int sqliteVdbeExec( break; } + /* Opcode: AggReset * P2 * + ** + ** Reset the aggregator so that it no longer contains any data. + ** Future aggregator elements will contain P2 values each. + */ + case OP_AggReset: { + AggReset(&p->agg); + p->agg.nMem = pOp->p2; + break; + } + + /* Opcode: AggFocus * P2 * + ** + ** Pop the top of the stack and use that as an aggregator key. If + ** an aggregator with that same key already exists, then make the + ** aggregator the current aggregator and jump to P2. If no aggregator + ** with the given key exists, create one and make it current but + ** do not jump. + ** + ** This opcode should not be executed after an AggNext but before + ** the next AggReset. + */ + case OP_AggFocus: { + int tos = p->tos; + AggElem *pElem; + char *zKey; + int nKey; + + if( tos<0 ) goto not_enough_stack; + Stringify(p, tos); + zKey = p->zStack[tos]; + nKey = p->aStack[tos].n; + if( p->agg.nHash<=0 ){ + pElem = 0; + }else{ + int h = sqliteHashNoCase(zKey, nKey-1) % p->agg.nHash; + for(pElem=p->agg.apHash[h]; pElem; pElem=pElem->pHash){ + if( strcmp(pElem->zKey, zKey)==0 ) break; + } + } + if( pElem ){ + p->agg.pCurrent = pElem; + pc = pOp->p2 - 1; + }else{ + AggInsert(&p->agg, zKey); + } + break; + } + + /* Opcode: AggIncr P1 P2 * + ** + ** Increment the P2-th field of the aggregate element current + ** in focus by an amount P1. + */ + case OP_AggIncr: { + AggElem *pFocus = AggInFocus(p->agg); + int i = pOp->p2; + if( pFocus==0 ) goto no_mem; + if( i>=0 && iagg.nMem ){ + Mem *pMem = &pFocus->aMem[i]; + if( pMem->s.flags!=STK_Int ){ + if( pMem->s.flags & STK_Int ){ + /* Do nothing */ + }else if( pMem->s.flags & STK_Real ){ + pMem->s.i = pMem->s.r; + }else if( pMem->s.flags & STK_Str ){ + pMem->s.i = atoi(pMem->z); + }else{ + pMem->s.i = 0; + } + if( pMem->s.flags & STK_Dyn ) sqliteFree(pMem->z); + pMem->z = 0; + pMem->s.flags = STK_Int; + } + pMem->s.i += pOp->p1; + } + break; + } + + /* Opcode: AggSet * P2 * + ** + ** Move the top of the stack into the P2-th field of the current + ** aggregate. String values are duplicated into new memory. + */ + case OP_AggSet: { + AggElem *pFocus = AggInFocus(p->agg); + int i = pOp->p2; + int tos = p->tos; + if( tos<0 ) goto not_enough_stack; + if( pFocus==0 ) goto no_mem; + if( i>=0 && iagg.nMem ){ + Mem *pMem = &pFocus->aMem[i]; + pMem->s = p->aStack[tos]; + if( pMem->s.flags & STK_Str ){ + pMem->z = sqliteMalloc( p->aStack[tos].n ); + if( pMem->z==0 ) goto no_mem; + memcpy(pMem->z, p->zStack[tos], pMem->s.n); + pMem->s.flags |= STK_Str|STK_Dyn; + } + } + PopStack(p, 1); + break; + } + + /* Opcode: AggGet * P2 * + ** + ** Push a new entry onto the stack which is a copy of the P2-th field + ** of the current aggregate. String are not duplicated so + ** string values will be ephemeral. + */ + case OP_AggGet: { + AggElem *pFocus = AggInFocus(p->agg); + int i = pOp->p2; + int tos = ++p->tos; + if( NeedStack(p, tos) ) goto no_mem; + if( pFocus==0 ) goto no_mem; + if( i>=0 && iagg.nMem ){ + Mem *pMem = &pFocus->aMem[i]; + p->aStack[tos] = pMem->s; + p->zStack[tos] = pMem->z; + p->aStack[tos].flags &= ~STK_Dyn; + } + break; + } + + /* Opcode: AggNext * P2 * + ** + ** Make the next aggregate value the current aggregate. The prior + ** aggregate is deleted. If all aggregate values have been consumed, + ** jump to P2. + ** + ** Do not execute an AggFocus after this opcode until after the + ** next AggReset. + */ + case OP_AggNext: { + if( p->agg.nHash ){ + p->agg.nHash = 0; + sqliteFree(p->agg.apHash); + p->agg.apHash = 0; + p->agg.pCurrent = p->agg.pFirst; + }else if( p->agg.pCurrent==p->agg.pFirst ){ + int i; + AggElem *pElem = p->agg.pCurrent; + for(i=0; iagg.nMem; i++){ + if( pElem->aMem[i].s.flags & STK_Dyn ){ + sqliteFree(pElem->aMem[i].z); + } + } + p->agg.pCurrent = p->agg.pFirst = pElem->pNext; + sqliteFree(pElem); + p->agg.nElem--; + } + if( p->agg.pCurrent==0 ){ + pc = pOp->p2-1; + } + break; + } + /* An other opcode is illegal... */ default: { diff --git a/src/vdbe.h b/src/vdbe.h index c85a1d3a81..9aaa43abb6 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -27,7 +27,7 @@ ** or VDBE. The VDBE implements an abstract machine that runs a ** simple program to access and modify the underlying database. ** -** $Id: vdbe.h,v 1.6 2000/06/05 18:54:47 drh Exp $ +** $Id: vdbe.h,v 1.7 2000/06/05 21:39:49 drh Exp $ */ #ifndef _SQLITE_VDBE_H_ #define _SQLITE_VDBE_H_ @@ -115,49 +115,56 @@ typedef struct VdbeOp VdbeOp; #define OP_FileField 36 #define OP_FileClose 37 -#define OP_MakeRecord 38 -#define OP_MakeKey 39 +#define OP_AggReset 38 +#define OP_AggFocus 39 +#define OP_AggIncr 40 +#define OP_AggNext 41 +#define OP_AggSet 42 +#define OP_AggGet 43 -#define OP_Goto 40 -#define OP_If 41 -#define OP_Halt 42 +#define OP_MakeRecord 44 +#define OP_MakeKey 45 -#define OP_ColumnCount 43 -#define OP_ColumnName 44 -#define OP_Callback 45 +#define OP_Goto 46 +#define OP_If 47 +#define OP_Halt 48 -#define OP_Integer 46 -#define OP_String 47 -#define OP_Null 48 -#define OP_Pop 49 -#define OP_Dup 50 -#define OP_Pull 51 +#define OP_ColumnCount 49 +#define OP_ColumnName 50 +#define OP_Callback 51 -#define OP_Add 52 -#define OP_AddImm 53 -#define OP_Subtract 54 -#define OP_Multiply 55 -#define OP_Divide 56 -#define OP_Min 57 -#define OP_Max 58 -#define OP_Like 59 -#define OP_Glob 60 -#define OP_Eq 61 -#define OP_Ne 62 -#define OP_Lt 63 -#define OP_Le 64 -#define OP_Gt 65 -#define OP_Ge 66 -#define OP_IsNull 67 -#define OP_NotNull 68 -#define OP_Negative 69 -#define OP_And 70 -#define OP_Or 71 -#define OP_Not 72 -#define OP_Concat 73 -#define OP_Noop 74 +#define OP_Integer 52 +#define OP_String 53 +#define OP_Null 54 +#define OP_Pop 55 +#define OP_Dup 56 +#define OP_Pull 57 -#define OP_MAX 74 +#define OP_Add 58 +#define OP_AddImm 59 +#define OP_Subtract 60 +#define OP_Multiply 61 +#define OP_Divide 62 +#define OP_Min 63 +#define OP_Max 64 +#define OP_Like 65 +#define OP_Glob 66 +#define OP_Eq 67 +#define OP_Ne 68 +#define OP_Lt 69 +#define OP_Le 70 +#define OP_Gt 71 +#define OP_Ge 72 +#define OP_IsNull 73 +#define OP_NotNull 74 +#define OP_Negative 75 +#define OP_And 76 +#define OP_Or 77 +#define OP_Not 78 +#define OP_Concat 79 +#define OP_Noop 80 + +#define OP_MAX 80 /* ** Prototypes for the VDBE interface. See comments on the implementation diff --git a/test/subselect.test b/test/subselect.test index 113fab5e33..5a436cd572 100644 --- a/test/subselect.test +++ b/test/subselect.test @@ -24,7 +24,7 @@ # focus of this file is testing SELECT statements that are part of # expressions. # -# $Id: subselect.test,v 1.2 2000/06/05 18:56:43 drh Exp $ +# $Id: subselect.test,v 1.3 2000/06/05 21:39:49 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -87,4 +87,12 @@ do_test subselect-1.5 { } } {8} +# Try something useful. Delete every entry from t2 where the +# x value is less than half of the maximum. +# +do_test subselect-1.6 { + execsql {DELETE FROM t2 WHERE x < 0.5*(SELECT max(x) FROM t2)} + execsql {SELECT x FROM t2 ORDER BY x} +} {2 3 4} + finish_test