From ade8648301787b43f30203d6a791cd9398a18f5d Mon Sep 17 00:00:00 2001 From: drh Date: Wed, 28 Nov 2007 22:36:40 +0000 Subject: [PATCH] Add the {quote: StrAccum} object for accumulating strings. Revamp xprintf to use the new object. Rewrite the group_concat() function to use the new object. Productize and test the group_concat() function. (CVS 4578) FossilOrigin-Name: 221aee72be040769e8026b91648f03c6366a8821 --- manifest | 22 ++-- manifest.uuid | 2 +- src/func.c | 44 ++++--- src/printf.c | 272 ++++++++++++++++++++----------------------- src/sqliteInt.h | 21 +++- test/func.test | 30 ++++- test/malloc.test | 4 +- test/sqllimits1.test | 15 ++- 8 files changed, 231 insertions(+), 179 deletions(-) diff --git a/manifest b/manifest index b754e50c48..e7e04da2cb 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Change\sthe\sBTree\sso\sthat\sit\suses\sthe\sPagers\stemporary\spage\sspace\swhen\nreorganizing\sthe\srows\son\sa\spage,\srather\sthan\smallocing\sfor\sspace\sof\nits\sown.\s\sIn\sthis\sway,\swe\savoid\shaving\sto\sdeal\swith\sa\smalloc\sfailure\ndeep\sdown\sinside\sthe\spage\sreorganizer.\s\sTicket\s#2806\s(CVS\s4577) -D 2007-11-28T16:19:56 +C Add\sthe\s{quote:\sStrAccum}\sobject\r\n\sfor\saccumulating\sstrings.\s\sRevamp\sxprintf\sto\suse\r\nthe\snew\sobject.\s\sRewrite\sthe\sgroup_concat()\sfunction\sto\suse\sthe\snew\sobject.\r\nProductize\sand\stest\sthe\sgroup_concat()\sfunction.\s(CVS\s4578) +D 2007-11-28T22:36:41 F Makefile.arm-wince-mingw32ce-gcc ac5f7b2cef0cd850d6f755ba6ee4ab961b1fadf7 F Makefile.in 35396fd58890420b29edcf27b6c0e2d054862a6b F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654 @@ -93,7 +93,7 @@ F src/date.c 49c5a6d2de6c12000905b4d36868b07d3011bbf6 F src/delete.c 034b87768c4135a22038a86a205f9d2d5f68a143 F src/experimental.c 1b2d1a6cd62ecc39610e97670332ca073c50792b F src/expr.c 7977bb9680ebeeabfa3214d936778baaa26dcc0c -F src/func.c 73b4974e5ff03cc71345cc3a33b0022f7b99974a +F src/func.c 49f98cfe26b9ab507b96a34404295c4d89dfc620 F src/hash.c 45a7005aac044b6c86bd7e49c44bc15d30006d6c F src/hash.h 031cd9f915aff27e12262cb9eb570ac1b8326b53 F src/insert.c a090c7258f2be707cca8f0cf376142f141621241 @@ -128,14 +128,14 @@ F src/pager.h f504f7ae84060fee0416a853e368d3d113c3d6fa F src/parse.y a780b33ef45dd7b3272319cf91e609d6f109a31c F src/pragma.c cb1486e76dbcad757968afc4083d3472032e62b5 F src/prepare.c f811fdb6fd4a82cca673a6e1d5b041d6caf567f1 -F src/printf.c 96c8d55315a13fc53cb3754cb15046f3ff891ea2 +F src/printf.c 0d7ad185914c952bfa6235ac8f5f019db5ad37e5 F src/random.c 4a22746501bf36b0a088c66e38dde5daba6a35da F src/select.c 7c0ab94b8f287eb94fdb1eb101be603832ecfc34 F src/server.c 087b92a39d883e3fa113cae259d64e4c7438bc96 F src/shell.c c97be281cfc3dcb14902f45e4b16f20038eb83ff F src/sqlite.h.in 75ae0863db3a0b074868a6157e34b646dbe143dd F src/sqlite3ext.h a93f59cdee3638dc0c9c086f80df743a4e68c3cb -F src/sqliteInt.h 22fb7a7271dbabca410d71c6ad80897f063356b1 +F src/sqliteInt.h c724e97dcc7d9c3881ce6443f243afccfe6b10a8 F src/sqliteLimit.h 15ffe2116746c27ace2b428a26a4fcd6dba6fa65 F src/table.c 1aeb9eab57b4235db86fe15a35dec76fb445a9c4 F src/tclsqlite.c 9923abeffc9b3d7dad58e92b319661521f60debf @@ -312,7 +312,7 @@ F test/fts3ao.test 0aa29dd4fc1c8d46b1f7cfe5926f7ac97551bea9 F test/fts3atoken.test 25c2070e1e8755d414bf9c8200427b277a9f99fa F test/fts3b.test b3a25180a633873d37d86e1ccd00ed690d37237a F test/fts3near.test 2d4dadcaac5025ab65bb87e66c45f39e92966194 -F test/func.test fd05232dffa77492c473f5a71d2cde6cb0ccfb1a +F test/func.test 4d54202f6a1c8498444d9efe460851b02a1e8e4f F test/fuzz.test 62fc19dd36a427777fd671b569df07166548628a F test/fuzz2.test ea38692ce2da99ad79fe0be5eb1a452c1c4d37bb F test/fuzz_common.tcl ff4bc2dfc465f6878f8e2d819620914365382731 @@ -356,7 +356,7 @@ F test/lock2.test 5f9557b775662c2a5ee435378f39e10d64f65cb3 F test/lock3.test 615111293cf32aa2ed16d01c6611737651c96fb9 F test/lock4.test f358fa835dff485d462072eee991111f09e87441 F test/main.test 05f585bb70c05caac3e047903b517cbb319ed204 -F test/malloc.test 93fca57780e221192c74e51c59c4291768f33897 +F test/malloc.test debc0cb41b7031b316353b949960a70e444fd301 F test/malloc2.test 850471731efad72af5a7748e366a371933ff0b64 F test/malloc3.test 3d690cbd66c93a3d41606ed8cfcbe1c9853e9d83 F test/malloc4.test f0e5e0f639f61e2776a6c3f5308f836b3ad8b3c7 @@ -429,7 +429,7 @@ F test/speed1.test 22e1b27af0683ed44dcd2f93ed817a9c3e65084a F test/speed2.test 53177056baf6556dcbdcf032bbdfc41c1aa74ded F test/speed3.test e312d7e442a5047d730569fdae2ba99bc94e1a13 F test/speed4.test 20d8ea20bea3ca09c3ef3b5ec820a17e58e132cb -F test/sqllimits1.test 3b08a538c9828041a5c1454293594d922602044d +F test/sqllimits1.test e7a6c34f6915c334a66db8065484d686dbabece0 F test/subquery.test 8203f85db56ba022a57a0589890090c8feed4e59 F test/subselect.test 974e87f8fc91c5f00dd565316d396a5a6c3106c4 F test/substr.test 4be572ac017143e59b4058dc75c91a0d0dc6d4e0 @@ -593,7 +593,7 @@ F www/tclsqlite.tcl 8be95ee6dba05eabcd27a9d91331c803f2ce2130 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0 F www/version3.tcl 890248cf7b70e60c383b0e84d77d5132b3ead42b F www/whentouse.tcl fc46eae081251c3c181bd79c5faef8195d7991a5 -P 542e11f954983ae26fef4ea850c8b2a20f738edd -R 21c1d3a78e0cb66c070fb743696cb747 +P 98960132dc082da61652201f4bd2b559725350c0 +R 099384e3de985a880eea8f610db48bc7 U drh -Z 6091ee38d7896a754234ddd4a74396ad +Z 25fbe708392dfcc01a46aeda08531786 diff --git a/manifest.uuid b/manifest.uuid index e680c75f3b..93f868d506 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -98960132dc082da61652201f4bd2b559725350c0 \ No newline at end of file +221aee72be040769e8026b91648f03c6366a8821 \ No newline at end of file diff --git a/src/func.c b/src/func.c index fcad3aadf0..6bf4ed1f8d 100644 --- a/src/func.c +++ b/src/func.c @@ -16,7 +16,7 @@ ** sqliteRegisterBuildinFunctions() found at the bottom of the file. ** All other code has file scope. ** -** $Id: func.c,v 1.176 2007/11/01 17:38:31 drh Exp $ +** $Id: func.c,v 1.177 2007/11/28 22:36:41 drh Exp $ */ #include "sqliteInt.h" #include @@ -1312,7 +1312,6 @@ static void minMaxFinalize(sqlite3_context *context){ } } -#ifdef SQLITE_GROUP_CONCAT /* ** group_concat(EXPR, ?SEPARATOR?) */ @@ -1322,32 +1321,43 @@ static void groupConcatStep( sqlite3_value **argv ){ const char *zVal; - char **pzAccumulator; + StrAccum *pAccum; const char *zSep; + int nVal, nSep; if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - zVal = sqlite3_value_text(argv[0]); - pzAccumulator = (char**)sqlite3_aggregate_context(context, sizeof(char*)); - if( pzAccumulator ){ - if( *pzAccumulator==0 ){ - *pzAccumulator = sqlite3_mprintf("%s", zVal); - }else{ + pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum)); + + if( pAccum ){ + pAccum->useMalloc = 1; + if( pAccum->nChar ){ if( argc==2 ){ - zSep = sqlite3_value_text(argv[1]); + zSep = (char*)sqlite3_value_text(argv[1]); + nSep = sqlite3_value_bytes(argv[1]); }else{ zSep = ","; + nSep = 1; } - *pzAccumulator = sqlite3_mprintf("%z%s%s", *pzAccumulator, zSep, zVal); + sqlite3StrAccumAppend(pAccum, zSep, nSep); } + zVal = (char*)sqlite3_value_text(argv[0]); + nVal = sqlite3_value_bytes(argv[0]); + sqlite3StrAccumAppend(pAccum, zVal, nVal); } } static void groupConcatFinalize(sqlite3_context *context){ - char **pzAccum; - pzAccum = sqlite3_aggregate_context(context, 0); - if( pzAccum ){ - sqlite3_result_text(context, *pzAccum, -1, sqlite3_free); + StrAccum *pAccum; + pAccum = sqlite3_aggregate_context(context, 0); + if( pAccum ){ + if( pAccum->tooBig ){ + sqlite3_result_error_toobig(context); + }else if( pAccum->mallocFailed ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_result_text(context, sqlite3StrAccumFinish(pAccum), -1, + sqlite3_free); + } } } -#endif /*SQLITE_GROUP_CONCAT*/ /* ** This function registered all of the above C functions as SQL @@ -1427,10 +1437,8 @@ void sqlite3RegisterBuiltinFunctions(sqlite3 *db){ { "avg", 1, 0, 0, sumStep, avgFinalize }, { "count", 0, 0, 0, countStep, countFinalize }, { "count", 1, 0, 0, countStep, countFinalize }, -#ifdef SQLITE_GROUP_CONCAT { "group_concat", 1, 0, 0, groupConcatStep, groupConcatFinalize }, { "group_concat", 2, 0, 0, groupConcatStep, groupConcatFinalize }, -#endif }; int i; diff --git a/src/printf.c b/src/printf.c index 839c24bbe5..260b3c2267 100644 --- a/src/printf.c +++ b/src/printf.c @@ -166,6 +166,20 @@ static int et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ } #endif /* SQLITE_OMIT_FLOATING_POINT */ +/* +** Append N space characters to the given string buffer. +*/ +static void appendSpace(StrAccum *pAccum, int N){ + static const char zSpaces[] = " "; + while( N>=sizeof(zSpaces)-1 ){ + sqlite3StrAccumAppend(pAccum, zSpaces, sizeof(zSpaces)-1); + N -= sizeof(zSpaces)-1; + } + if( N>0 ){ + sqlite3StrAccumAppend(pAccum, zSpaces, N); + } +} + /* ** On machines with a small stack size, you can redefine the ** SQLITE_PRINT_BUF_SIZE to be less than 350. But beware - for @@ -203,9 +217,8 @@ static int et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ ** seems to make a big difference in determining how fast this beast ** will run. */ -static int vxprintf( - void (*func)(void*,const char*,int), /* Consumer of text */ - void *arg, /* First argument to the consumer */ +static void vxprintf( + StrAccum *pAccum, /* Accumulate results here */ int useExtended, /* Allow extended %-conversions */ const char *fmt, /* Format string */ va_list ap /* arguments */ @@ -215,7 +228,6 @@ static int vxprintf( int precision; /* Precision of the current field */ int length; /* Length of the field */ int idx; /* A general purpose loop counter */ - int count; /* Total number of characters output */ int width; /* Width of the current field */ etByte flag_leftjustify; /* True if "-" flag is present */ etByte flag_plussign; /* True if "+" flag is present */ @@ -234,9 +246,6 @@ static int vxprintf( etByte errorflag = 0; /* True if an error is encountered */ etByte xtype; /* Conversion paradigm */ char *zExtra; /* Extra memory used for etTCLESCAPE conversions */ - static const char spaces[] = - " "; -#define etSPACESIZE (sizeof(spaces)-1) #ifndef SQLITE_OMIT_FLOATING_POINT int exp, e2; /* exponent of real numbers */ double rounder; /* Used for rounding floating point values */ @@ -246,8 +255,7 @@ static int vxprintf( int nsd; /* Number of significant digits returned */ #endif - func(arg,"",0); - count = length = 0; + length = 0; bufpt = 0; for(; (c=(*fmt))!=0; ++fmt){ if( c!='%' ){ @@ -255,14 +263,12 @@ static int vxprintf( bufpt = (char *)fmt; amt = 1; while( (c=(*++fmt))!='%' && c!=0 ) amt++; - (*func)(arg,bufpt,amt); - count += amt; + sqlite3StrAccumAppend(pAccum, bufpt, amt); if( c==0 ) break; } if( (c=(*++fmt))==0 ){ errorflag = 1; - (*func)(arg,"%",1); - count++; + sqlite3StrAccumAppend(pAccum, "%", 1); break; } /* Find out what flags are present */ @@ -336,14 +342,14 @@ static int vxprintf( if( useExtended || (infop->flags & FLAG_INTERN)==0 ){ xtype = infop->type; }else{ - return -1; + return; } break; } } zExtra = 0; if( infop==0 ){ - return -1; + return; } @@ -581,7 +587,7 @@ static int vxprintf( #endif break; case etSIZE: - *(va_arg(ap,int*)) = count; + *(va_arg(ap,int*)) = pAccum->nChar; length = width = 0; break; case etPERCENT: @@ -627,7 +633,7 @@ static int vxprintf( n += i + 1 + needQuote*2; if( n>etBUFSIZE ){ bufpt = zExtra = sqlite3_malloc( n ); - if( bufpt==0 ) return -1; + if( bufpt==0 ) return; }else{ bufpt = buf; } @@ -647,7 +653,7 @@ static int vxprintf( case etTOKEN: { Token *pToken = va_arg(ap, Token*); if( pToken && pToken->z ){ - (*func)(arg, (char*)pToken->z, pToken->n); + sqlite3StrAccumAppend(pAccum, (const char*)pToken->z, pToken->n); } length = width = 0; break; @@ -658,10 +664,10 @@ static int vxprintf( struct SrcList_item *pItem = &pSrc->a[k]; assert( k>=0 && knSrc ); if( pItem->zDatabase && pItem->zDatabase[0] ){ - (*func)(arg, pItem->zDatabase, strlen(pItem->zDatabase)); - (*func)(arg, ".", 1); + sqlite3StrAccumAppend(pAccum, pItem->zDatabase, -1); + sqlite3StrAccumAppend(pAccum, ".", 1); } - (*func)(arg, pItem->zName, strlen(pItem->zName)); + sqlite3StrAccumAppend(pAccum, pItem->zName, -1); length = width = 0; break; } @@ -675,143 +681,112 @@ static int vxprintf( register int nspace; nspace = width-length; if( nspace>0 ){ - count += nspace; - while( nspace>=etSPACESIZE ){ - (*func)(arg,spaces,etSPACESIZE); - nspace -= etSPACESIZE; - } - if( nspace>0 ) (*func)(arg,spaces,nspace); + appendSpace(pAccum, nspace); } } if( length>0 ){ - (*func)(arg,bufpt,length); - count += length; + sqlite3StrAccumAppend(pAccum, bufpt, length); } if( flag_leftjustify ){ register int nspace; nspace = width-length; if( nspace>0 ){ - count += nspace; - while( nspace>=etSPACESIZE ){ - (*func)(arg,spaces,etSPACESIZE); - nspace -= etSPACESIZE; - } - if( nspace>0 ) (*func)(arg,spaces,nspace); + appendSpace(pAccum, nspace); } } if( zExtra ){ sqlite3_free(zExtra); } }/* End for loop over the format string */ - return errorflag ? -1 : count; } /* End of function */ - -/* This structure is used to store state information about the -** write to memory that is currently in progress. +/* +** Append N bytes of text from z to the StrAccum object. */ -struct sgMprintf { - char *zBase; /* A base allocation */ - char *zText; /* The string collected so far */ - int nChar; /* Length of the string so far */ - int nTotal; /* Output size if unconstrained */ - int nAlloc; /* Amount of space allocated in zText */ - void *(*xRealloc)(void*,int); /* Function used to realloc memory */ - int iMallocFailed; /* True if xRealloc() has failed */ -}; - -/* -** This function implements the callback from vxprintf. -** -** This routine add nNewChar characters of text in zNewText to -** the sgMprintf structure pointed to by "arg". -*/ -static void mout(void *arg, const char *zNewText, int nNewChar){ - struct sgMprintf *pM = (struct sgMprintf*)arg; - if( pM->iMallocFailed ) return; - pM->nTotal += nNewChar; - if( pM->zText ){ - if( pM->nChar + nNewChar + 1 > pM->nAlloc ){ - if( pM->xRealloc==0 ){ - nNewChar = pM->nAlloc - pM->nChar - 1; - }else{ - int nAlloc = pM->nChar + nNewChar*2 + 1; - if( pM->zText==pM->zBase ){ - pM->zText = pM->xRealloc(0, nAlloc); - if( pM->zText==0 ){ - pM->nAlloc = 0; - pM->iMallocFailed = 1; - return; - }else if( pM->nChar ){ - memcpy(pM->zText, pM->zBase, pM->nChar); - } - }else{ - char *zNew; - zNew = pM->xRealloc(pM->zText, nAlloc); - if( zNew ){ - pM->zText = zNew; - }else{ - pM->iMallocFailed = 1; - pM->xRealloc(pM->zText, 0); - pM->zText = 0; - pM->nAlloc = 0; - return; - } +void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){ + if( p->tooBig | p->mallocFailed ){ + return; + } + if( N<0 ){ + N = strlen(z); + } + if( N==0 ){ + return; + } + if( p->nChar+N >= p->nAlloc ){ + char *zNew; + if( !p->useMalloc ){ + p->tooBig = 1; + N = p->nAlloc - p->nChar - 1; + if( N<=0 ){ + return; + } + }else{ + p->nAlloc += p->nAlloc + N + 1; + if( p->nAlloc > SQLITE_MAX_LENGTH ){ + p->nAlloc = SQLITE_MAX_LENGTH; + if( p->nChar+N >= p->nAlloc ){ + sqlite3StrAccumReset(p); + p->tooBig = 1; + return; } - pM->nAlloc = nAlloc; } - } - if( nNewChar>0 ){ - memcpy(&pM->zText[pM->nChar], zNewText, nNewChar); - pM->nChar += nNewChar; - } - pM->zText[pM->nChar] = 0; - } -} - -/* -** This routine is a wrapper around xprintf() that invokes mout() as -** the consumer. -*/ -static char *base_vprintf( - void *(*xRealloc)(void*, int), /* realloc() function. May be NULL */ - int useInternal, /* Use internal %-conversions if true */ - char *zInitBuf, /* Initially write here, before mallocing */ - int nInitBuf, /* Size of zInitBuf[] */ - const char *zFormat, /* format string */ - va_list ap /* arguments */ -){ - struct sgMprintf sM; - sM.zBase = sM.zText = zInitBuf; - sM.nChar = sM.nTotal = 0; - sM.nAlloc = nInitBuf; - sM.xRealloc = xRealloc; - sM.iMallocFailed = 0; - vxprintf(mout, &sM, useInternal, zFormat, ap); - assert(sM.iMallocFailed==0 || sM.zText==0); - if( xRealloc && !sM.iMallocFailed ){ - if( sM.zText==sM.zBase ){ - sM.zText = xRealloc(0, sM.nChar+1); - if( sM.zText ){ - memcpy(sM.zText, sM.zBase, sM.nChar+1); - } - }else if( sM.nAlloc>sM.nChar+10 ){ - char *zNew; - sqlite3MallocBenignFailure(1); - zNew = xRealloc(sM.zText, sM.nChar+1); + zNew = sqlite3_malloc( p->nAlloc ); if( zNew ){ - sM.zText = zNew; + memcpy(zNew, p->zText, p->nChar); + sqlite3StrAccumReset(p); + p->zText = zNew; + }else{ + p->mallocFailed = 1; + sqlite3StrAccumReset(p); + return; } } } - return sM.zText; + memcpy(&p->zText[p->nChar], z, N); + p->nChar += N; } /* -** Realloc that is a real function, not a macro. +** Finish off a string by making sure it is zero-terminated. +** Return a pointer to the resulting string. Return a NULL +** pointer if any kind of error was encountered. */ -static void *printf_realloc(void *old, int size){ - return sqlite3_realloc(old, size); +char *sqlite3StrAccumFinish(StrAccum *p){ + if( p->zText ){ + p->zText[p->nChar] = 0; + if( p->useMalloc && p->zText==p->zBase ){ + p->zText = sqlite3_malloc( p->nChar+1 ); + if( p->zText ){ + memcpy(p->zText, p->zBase, p->nChar+1); + }else{ + p->mallocFailed = 1; + } + } + } + return p->zText; +} + +/* +** Reset an StrAccum string. Reclaim all malloced memory. +*/ +void sqlite3StrAccumReset(StrAccum *p){ + if( p->zText!=p->zBase ){ + sqlite3_free(p->zText); + p->zText = 0; + } +} + +/* +** Initialize a string accumulator +*/ +static void sqlite3StrAccumInit(StrAccum *p, char *zBase, int n){ + p->zText = p->zBase = zBase; + p->nChar = 0; + p->nAlloc = n; + p->useMalloc = 1; + p->tooBig = 0; + p->mallocFailed = 0; } /* @@ -821,8 +796,11 @@ static void *printf_realloc(void *old, int size){ char *sqlite3VMPrintf(sqlite3 *db, const char *zFormat, va_list ap){ char *z; char zBase[SQLITE_PRINT_BUF_SIZE]; - z = base_vprintf(printf_realloc, 1, zBase, sizeof(zBase), zFormat, ap); - if( z==0 && db!=0 ){ + StrAccum acc; + sqlite3StrAccumInit(&acc, zBase, sizeof(zBase)); + vxprintf(&acc, 1, zFormat, ap); + z = sqlite3StrAccumFinish(&acc); + if( acc.mallocFailed && db ){ db->mallocFailed = 1; } return z; @@ -835,13 +813,9 @@ char *sqlite3VMPrintf(sqlite3 *db, const char *zFormat, va_list ap){ char *sqlite3MPrintf(sqlite3 *db, const char *zFormat, ...){ va_list ap; char *z; - char zBase[SQLITE_PRINT_BUF_SIZE]; va_start(ap, zFormat); - z = base_vprintf(printf_realloc, 1, zBase, sizeof(zBase), zFormat, ap); + z = sqlite3VMPrintf(db, zFormat, ap); va_end(ap); - if( z==0 && db!=0 ){ - db->mallocFailed = 1; - } return z; } @@ -850,8 +824,13 @@ char *sqlite3MPrintf(sqlite3 *db, const char *zFormat, ...){ ** %-conversion extensions. */ char *sqlite3_vmprintf(const char *zFormat, va_list ap){ + char *z; char zBase[SQLITE_PRINT_BUF_SIZE]; - return base_vprintf(sqlite3_realloc, 0, zBase, sizeof(zBase), zFormat, ap); + StrAccum acc; + sqlite3StrAccumInit(&acc, zBase, sizeof(zBase)); + vxprintf(&acc, 0, zFormat, ap); + z = sqlite3StrAccumFinish(&acc); + return z; } /* @@ -876,14 +855,17 @@ char *sqlite3_mprintf(const char *zFormat, ...){ char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ char *z; va_list ap; + StrAccum acc; if( n<=0 ){ return zBuf; } - zBuf[0] = 0; + sqlite3StrAccumInit(&acc, zBuf, n); + acc.useMalloc = 0; va_start(ap,zFormat); - z = base_vprintf(0, 0, zBuf, n, zFormat, ap); + vxprintf(&acc, 0, zFormat, ap); va_end(ap); + z = sqlite3StrAccumFinish(&acc); return z; } @@ -894,11 +876,13 @@ char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ ** and segfaults if you give it a long long int. */ void sqlite3DebugPrintf(const char *zFormat, ...){ - extern int getpid(void); va_list ap; + StrAccum acc; char zBuf[500]; - va_start(ap, zFormat); - base_vprintf(0, 0, zBuf, sizeof(zBuf), zFormat, ap); + sqlite3StrAccumInit(&acc, zBuf, sizeof(zBuf)); + acc.useMalloc = 0; + va_start(ap,zFormat); + vxprintf(&acc, 0, zFormat, ap); va_end(ap); fprintf(stdout,"%s", zBuf); fflush(stdout); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index c27b632c82..f1142493e8 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.620 2007/11/27 02:38:01 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.621 2007/11/28 22:36:41 drh Exp $ */ #ifndef _SQLITEINT_H_ #define _SQLITEINT_H_ @@ -363,6 +363,7 @@ typedef struct NameContext NameContext; typedef struct Parse Parse; typedef struct Select Select; typedef struct SrcList SrcList; +typedef struct StrAccum StrAccum; typedef struct Table Table; typedef struct TableLock TableLock; typedef struct Token Token; @@ -1573,6 +1574,20 @@ struct DbFixer { const Token *pName; /* Name of the container - used for error messages */ }; +/* +** An objected used to accumulate the text of a string where we +** do not necessarily know how big the string will be in the end. +*/ +struct StrAccum { + char *zBase; /* A base allocation. Not from malloc. */ + char *zText; /* The string collected so far */ + int nChar; /* Length of the string so far */ + int nAlloc; /* Amount of space allocated in zText */ + u8 mallocFailed; /* Becomes true if any memory allocation fails */ + u8 useMalloc; /* True if zText is enlargable using realloc */ + u8 tooBig; /* Becomes true if string size exceeds limits */ +}; + /* ** A pointer to this structure is used to communicate information ** from sqlite3Init and OP_ParseSchema into the sqlite3InitCallback. @@ -1879,6 +1894,10 @@ int sqlite3ApiExit(sqlite3 *db, int); void sqlite3AbortOtherActiveVdbes(sqlite3 *, Vdbe *); int sqlite3OpenTempDatabase(Parse *); +void sqlite3StrAccumAppend(StrAccum*,const char*,int); +char *sqlite3StrAccumFinish(StrAccum*); +void sqlite3StrAccumReset(StrAccum*); + /* ** The interface to the LEMON-generated parser diff --git a/test/func.test b/test/func.test index 8af5a88d2f..8ae7edd702 100644 --- a/test/func.test +++ b/test/func.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this file is testing built-in functions. # -# $Id: func.test,v 1.70 2007/10/20 15:41:58 drh Exp $ +# $Id: func.test,v 1.71 2007/11/28 22:36:41 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -884,4 +884,32 @@ do_test func-23.1 { } } {3} +# The group_concat() function. +# +do_test func-24.1 { + execsql { + SELECT group_concat(t1) FROM tbl1 + } +} {this,program,is,free,software} +do_test func-24.2 { + execsql { + SELECT group_concat(t1,' ') FROM tbl1 + } +} {{this program is free software}} +do_test func-24.3 { + execsql { + SELECT group_concat(t1,' ' || rowid || ' ') FROM tbl1 + } +} {{this 2 program 3 is 4 free 5 software}} +do_test func-24.4 { + execsql { + SELECT group_concat(NULL,t1) FROM tbl1 + } +} {{}} +do_test func-24.5 { + execsql { + SELECT group_concat(t1,NULL) FROM tbl1 + } +} {thisprogramisfreesoftware} + finish_test diff --git a/test/malloc.test b/test/malloc.test index fbef0df7f9..454601934f 100644 --- a/test/malloc.test +++ b/test/malloc.test @@ -16,7 +16,7 @@ # to see what happens in the library if a malloc were to really fail # due to an out-of-memory situation. # -# $Id: malloc.test,v 1.52 2007/11/16 14:55:46 danielk1977 Exp $ +# $Id: malloc.test,v 1.53 2007/11/28 22:36:41 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -51,7 +51,7 @@ ifcapable bloblit&&subquery { SELECT * FROM t1; SELECT avg(b) FROM t1 GROUP BY a HAVING b>20.0; DELETE FROM t1 WHERE a IN (SELECT min(a) FROM t1); - SELECT count(*) FROM t1; + SELECT count(*), group_concat(e) FROM t1; } } diff --git a/test/sqllimits1.test b/test/sqllimits1.test index 81e10c8959..27f5dadd31 100644 --- a/test/sqllimits1.test +++ b/test/sqllimits1.test @@ -12,7 +12,7 @@ # This file contains tests to verify that the limits defined in # sqlite source file limits.h are enforced. # -# $Id: sqllimits1.test,v 1.19 2007/10/09 08:29:33 danielk1977 Exp $ +# $Id: sqllimits1.test,v 1.20 2007/11/28 22:36:41 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -128,6 +128,19 @@ do_test sqllimits-1.14.3 { sqlite3_finalize $::STMT } {SQLITE_TOOBIG} +do_test sqllimits-1.15 { + execsql { + CREATE TABLE t4(x); + INSERT INTO t4 VALUES(1); + INSERT INTO t4 VALUES(2); + INSERT INTO t4 SELECT 2+x FROM t4; + } + catchsql { + SELECT group_concat(hex(randomblob(20000))) FROM t4; + } +} {1 {string or blob too big}} +db eval {DROP TABLE t4} + #-------------------------------------------------------------------- # Test cases sqllimits-2.* test that the SQLITE_MAX_SQL_LENGTH limit # is enforced.