mirror of
https://github.com/sqlite/sqlite.git
synced 2025-11-12 13:01:09 +03:00
Fix a problem with group_concat() when it is used as a window function with
a sliding window, as described by [forum:/forumpost/f3eb24a6c0|forum thread f3eb24a6c0]. FossilOrigin-Name: f47f7f78227830c065d9ce715b8456eab81a38d680f76bf4ff08f298d84f9c7a
This commit is contained in:
22
manifest
22
manifest
@@ -1,5 +1,5 @@
|
|||||||
C Fix\sharmless\scompiler\swarnings.
|
C Fix\sa\sproblem\swith\sgroup_concat()\swhen\sit\sis\sused\sas\sa\swindow\sfunction\swith\na\ssliding\swindow,\sas\sdescribed\sby\n[forum:/forumpost/f3eb24a6c0|forum\sthread\sf3eb24a6c0].
|
||||||
D 2021-10-01T21:01:07.786
|
D 2021-10-01T22:48:52.993
|
||||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||||
@@ -504,7 +504,7 @@ F src/delete.c 3ce6af6b64c8b476de51ccc32da0cb3142d42e65754e1d8118addf65b8bcba15
|
|||||||
F src/expr.c 82797e5d82422d34ede9a95ba459f40c317b2daadb21109a21abfd42f84e3ed8
|
F src/expr.c 82797e5d82422d34ede9a95ba459f40c317b2daadb21109a21abfd42f84e3ed8
|
||||||
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
|
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
|
||||||
F src/fkey.c 1905af1821b88321e1bb9d6a69e704495b6844a9b6c29398d40117cc251e893c
|
F src/fkey.c 1905af1821b88321e1bb9d6a69e704495b6844a9b6c29398d40117cc251e893c
|
||||||
F src/func.c 812ac5383067bed7150d8597e83c47b714d73db0e62af55811d1a145243e58e1
|
F src/func.c f4924c04e95335762ed1b24f184df26d0db5b20e87decbc5a0ed6431f7ebd9ca
|
||||||
F src/global.c 612ea60c9acbcb45754c2ed659b4a56936a06814718e969636fedc7e3b889808
|
F src/global.c 612ea60c9acbcb45754c2ed659b4a56936a06814718e969636fedc7e3b889808
|
||||||
F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19
|
F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19
|
||||||
F src/hash.h 9d56a9079d523b648774c1784b74b89bd93fac7b365210157482e4319a468f38
|
F src/hash.h 9d56a9079d523b648774c1784b74b89bd93fac7b365210157482e4319a468f38
|
||||||
@@ -545,7 +545,7 @@ F src/pcache1.c 54881292a9a5db202b2c0ac541c5e3ef9a5e8c4f1c1383adb2601d5499a60e65
|
|||||||
F src/pragma.c 4a473feae3646063996ce3bfae78032009fa950765908d97424f7578b202813c
|
F src/pragma.c 4a473feae3646063996ce3bfae78032009fa950765908d97424f7578b202813c
|
||||||
F src/pragma.h b33c7a542ae7965c471f0d3c0565ce4d340c3f32cc162f44133539b6b0edb927
|
F src/pragma.h b33c7a542ae7965c471f0d3c0565ce4d340c3f32cc162f44133539b6b0edb927
|
||||||
F src/prepare.c 8f07616db04337057b8498b72d051ee90f73c54615c2e908c05404cef1e060b7
|
F src/prepare.c 8f07616db04337057b8498b72d051ee90f73c54615c2e908c05404cef1e060b7
|
||||||
F src/printf.c 78fabb49b9ac9a12dd1c89d744abdc9b67fd3205e62967e158f78b965a29ec4b
|
F src/printf.c 1ce574bf02b503b0e031a70d3453324a9f4a0eb1ad379f3324ced73b918ed20b
|
||||||
F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
|
F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
|
||||||
F src/resolve.c b9e60afa56d0484ee573aba54d9e73603736236df33d2ae3421b4cd0367d907d
|
F src/resolve.c b9e60afa56d0484ee573aba54d9e73603736236df33d2ae3421b4cd0367d907d
|
||||||
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
|
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
|
||||||
@@ -554,7 +554,7 @@ F src/shell.c.in 928114971b558dc2e40a1afaf07e3e5e93558a77ecca95067c51e1f9d3c1441
|
|||||||
F src/sqlite.h.in 4e977a5e2ed1a9e8987ff65a2cab5f99a4298ebf040ea5ff636e1753339ff45a
|
F src/sqlite.h.in 4e977a5e2ed1a9e8987ff65a2cab5f99a4298ebf040ea5ff636e1753339ff45a
|
||||||
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
||||||
F src/sqlite3ext.h e97f4e9b509408fea4c4e9bef5a41608dfac343b4d3c7a990dedde1e19af9510
|
F src/sqlite3ext.h e97f4e9b509408fea4c4e9bef5a41608dfac343b4d3c7a990dedde1e19af9510
|
||||||
F src/sqliteInt.h c481cd4864217bdff96fc2a7054cdaca0edabe0eb9f6cce8b012d1e014ccdc5f
|
F src/sqliteInt.h 2cfc99065003dd1f5d91a85114449f920fde0adbea4bdbd524fca8907a6e38f3
|
||||||
F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
|
F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
|
||||||
F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
|
F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
|
||||||
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
|
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
|
||||||
@@ -1799,10 +1799,11 @@ F test/window8.tcl 5e02e41d9d9a80f597063aed1a381eb19d1d0ef677a4f0df352c5365cf23f
|
|||||||
F test/window8.test 4ab16817414af0c904abe2ebdf88eb6c2b00058b84f9748c6174ff11fc45f1ed
|
F test/window8.test 4ab16817414af0c904abe2ebdf88eb6c2b00058b84f9748c6174ff11fc45f1ed
|
||||||
F test/window9.test 349c71eab4288a1ffc19e2f65872ec2c37e6cf8a1dda2ad300364b7450ae4836
|
F test/window9.test 349c71eab4288a1ffc19e2f65872ec2c37e6cf8a1dda2ad300364b7450ae4836
|
||||||
F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be
|
F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be
|
||||||
F test/windowB.test 6e601f8178ba8ba28b2f19e74fe613815084bb4a8d2ad942defc7d42e191e521
|
F test/windowB.test b67bda5645f3226790e1a360c4225241840b84adb5aa2e69bfb0b27eef3b84d9
|
||||||
|
F test/windowC.test ecf1831b995408b03f708386b37ece7a05108faf2288c0c55cff873c100e145f
|
||||||
F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387019e0
|
F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387019e0
|
||||||
F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b
|
F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b
|
||||||
F test/windowfault.test 21919e601f20b976ea2a73aa401220c89ed0e8d203c4f69476ea55bce3726496
|
F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c31660a7c
|
||||||
F test/windowpushd.test d8895d08870b7226f7693665bd292eb177e62ca06799184957b3ca7dc03067df
|
F test/windowpushd.test d8895d08870b7226f7693665bd292eb177e62ca06799184957b3ca7dc03067df
|
||||||
F test/with1.test 7bc5abfe4c80c0cef8a90f5a66d60b9982e8ccd7350c8eb70611323a3b8e07ba
|
F test/with1.test 7bc5abfe4c80c0cef8a90f5a66d60b9982e8ccd7350c8eb70611323a3b8e07ba
|
||||||
F test/with2.test a1df41b987198383b9b70bf5e5fda390582e46398653858dbc6ceb24253b28df
|
F test/with2.test a1df41b987198383b9b70bf5e5fda390582e46398653858dbc6ceb24253b28df
|
||||||
@@ -1928,7 +1929,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
|||||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||||
P 2f7c946c5f25a858167f5193ca06e53310394c8cff15426ab8a1327a1ec835fd
|
P 94b59691ee50a4666b25e36d1529fc52f714bbe94c3e8ccb35bf0a4ea11050db 8bd721c29e7a28cd75885dd80235e4e37aa0847229f769f49e01127f794fa63a
|
||||||
R efed3f9c2718d18bc8a69a58b9e2f371
|
R 755a222a3849cf0b99ca8b66d427fd7c
|
||||||
|
T +closed 8bd721c29e7a28cd75885dd80235e4e37aa0847229f769f49e01127f794fa63a
|
||||||
U drh
|
U drh
|
||||||
Z 00ef577a561ca93dc97c1f48c37c36a1
|
Z d91f8598d681fe6b47f22c72a76f272f
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
94b59691ee50a4666b25e36d1529fc52f714bbe94c3e8ccb35bf0a4ea11050db
|
f47f7f78227830c065d9ce715b8456eab81a38d680f76bf4ff08f298d84f9c7a
|
||||||
143
src/func.c
143
src/func.c
@@ -1715,97 +1715,172 @@ static void minMaxFinalize(sqlite3_context *context){
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
** group_concat(EXPR, ?SEPARATOR?)
|
** group_concat(EXPR, ?SEPARATOR?)
|
||||||
|
**
|
||||||
|
** The SEPARATOR goes before the EXPR string. This is tragic. The
|
||||||
|
** groupConcatInverse() implementation would have been easier if the
|
||||||
|
** SEPARATOR were appended after EXPR. And the order is undocumented,
|
||||||
|
** so we could change it, in theory. But the old behavior has been
|
||||||
|
** around for so long that we dare not, for fear of breaking something.
|
||||||
*/
|
*/
|
||||||
|
typedef struct {
|
||||||
|
StrAccum str; /* The accumulated concatenation */
|
||||||
|
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||||
|
int nAccum; /* Number of strings presently concatenated */
|
||||||
|
int nFirstSepLength; /* Used to detect separator length change */
|
||||||
|
/* If pnSepLengths!=0, refs an array of inter-string separator lengths,
|
||||||
|
** stored as actually incorporated into presently accumulated result.
|
||||||
|
** (Hence, its slots in use number nAccum-1 between method calls.)
|
||||||
|
** If pnSepLengths==0, nFirstSepLength is the length used throughout.
|
||||||
|
*/
|
||||||
|
int *pnSepLengths;
|
||||||
|
#endif
|
||||||
|
} GroupConcatCtx;
|
||||||
|
|
||||||
static void groupConcatStep(
|
static void groupConcatStep(
|
||||||
sqlite3_context *context,
|
sqlite3_context *context,
|
||||||
int argc,
|
int argc,
|
||||||
sqlite3_value **argv
|
sqlite3_value **argv
|
||||||
){
|
){
|
||||||
const char *zVal;
|
const char *zVal;
|
||||||
StrAccum *pAccum;
|
GroupConcatCtx *pGCC;
|
||||||
const char *zSep;
|
const char *zSep;
|
||||||
int nVal, nSep;
|
int nVal, nSep;
|
||||||
assert( argc==1 || argc==2 );
|
assert( argc==1 || argc==2 );
|
||||||
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
|
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
|
||||||
pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum));
|
pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC));
|
||||||
|
if( pGCC ){
|
||||||
if( pAccum ){
|
|
||||||
sqlite3 *db = sqlite3_context_db_handle(context);
|
sqlite3 *db = sqlite3_context_db_handle(context);
|
||||||
int firstTerm = pAccum->mxAlloc==0;
|
int firstTerm = pGCC->str.mxAlloc==0;
|
||||||
pAccum->mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH];
|
pGCC->str.mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH];
|
||||||
|
if( argc==1 ){
|
||||||
if( !firstTerm ){
|
if( !firstTerm ){
|
||||||
if( argc==2 ){
|
sqlite3_str_appendchar(&pGCC->str, 1, ',');
|
||||||
|
}
|
||||||
|
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||||
|
else{
|
||||||
|
pGCC->nFirstSepLength = 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}else if( !firstTerm ){
|
||||||
zSep = (char*)sqlite3_value_text(argv[1]);
|
zSep = (char*)sqlite3_value_text(argv[1]);
|
||||||
nSep = sqlite3_value_bytes(argv[1]);
|
nSep = sqlite3_value_bytes(argv[1]);
|
||||||
|
if( zSep ){
|
||||||
|
sqlite3_str_append(&pGCC->str, zSep, nSep);
|
||||||
|
}
|
||||||
|
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||||
|
else{
|
||||||
|
nSep = 0;
|
||||||
|
}
|
||||||
|
if( nSep != pGCC->nFirstSepLength || pGCC->pnSepLengths != 0 ){
|
||||||
|
int *pnsl = pGCC->pnSepLengths;
|
||||||
|
if( pnsl == 0 ){
|
||||||
|
/* First separator length variation seen, start tracking them. */
|
||||||
|
pnsl = (int*)sqlite3_malloc64((pGCC->nAccum+1) * sizeof(int));
|
||||||
|
if( pnsl!=0 ){
|
||||||
|
int i = 0, nA = pGCC->nAccum-1;
|
||||||
|
while( i<nA ) pnsl[i++] = pGCC->nFirstSepLength;
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
zSep = ",";
|
pnsl = (int*)sqlite3_realloc64(pnsl, pGCC->nAccum * sizeof(int));
|
||||||
nSep = 1;
|
|
||||||
}
|
}
|
||||||
if( zSep ) sqlite3_str_append(pAccum, zSep, nSep);
|
if( pnsl!=0 ){
|
||||||
|
if( ALWAYS(pGCC->nAccum>0) ){
|
||||||
|
pnsl[pGCC->nAccum-1] = nSep;
|
||||||
}
|
}
|
||||||
|
pGCC->pnSepLengths = pnsl;
|
||||||
|
}else{
|
||||||
|
sqlite3StrAccumSetError(&pGCC->str, SQLITE_NOMEM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||||
|
else{
|
||||||
|
pGCC->nFirstSepLength = sqlite3_value_bytes(argv[1]);
|
||||||
|
}
|
||||||
|
pGCC->nAccum += 1;
|
||||||
|
#endif
|
||||||
zVal = (char*)sqlite3_value_text(argv[0]);
|
zVal = (char*)sqlite3_value_text(argv[0]);
|
||||||
nVal = sqlite3_value_bytes(argv[0]);
|
nVal = sqlite3_value_bytes(argv[0]);
|
||||||
if( zVal ) sqlite3_str_append(pAccum, zVal, nVal);
|
if( zVal ) sqlite3_str_append(&pGCC->str, zVal, nVal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef SQLITE_OMIT_WINDOWFUNC
|
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||||
static void groupConcatInverse(
|
static void groupConcatInverse(
|
||||||
sqlite3_context *context,
|
sqlite3_context *context,
|
||||||
int argc,
|
int argc,
|
||||||
sqlite3_value **argv
|
sqlite3_value **argv
|
||||||
){
|
){
|
||||||
int n;
|
GroupConcatCtx *pGCC;
|
||||||
StrAccum *pAccum;
|
|
||||||
assert( argc==1 || argc==2 );
|
assert( argc==1 || argc==2 );
|
||||||
|
(void)argc; /* Suppress unused parameter warning */
|
||||||
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
|
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
|
||||||
pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum));
|
pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC));
|
||||||
/* pAccum is always non-NULL since groupConcatStep() will have always
|
/* pGCC is always non-NULL since groupConcatStep() will have always
|
||||||
** run frist to initialize it */
|
** run frist to initialize it */
|
||||||
if( ALWAYS(pAccum) ){
|
if( ALWAYS(pGCC) ){
|
||||||
n = sqlite3_value_bytes(argv[0]);
|
int nVS = sqlite3_value_bytes(argv[0]);
|
||||||
if( argc==2 ){
|
pGCC->nAccum -= 1;
|
||||||
n += sqlite3_value_bytes(argv[1]);
|
if( pGCC->pnSepLengths!=0 ){
|
||||||
}else{
|
assert(pGCC->nAccum >= 0);
|
||||||
n++;
|
if( pGCC->nAccum>0 ){
|
||||||
|
nVS += *pGCC->pnSepLengths;
|
||||||
|
memmove(pGCC->pnSepLengths, pGCC->pnSepLengths+1,
|
||||||
|
(pGCC->nAccum-1)*sizeof(int));
|
||||||
}
|
}
|
||||||
if( n>=(int)pAccum->nChar ){
|
|
||||||
pAccum->nChar = 0;
|
|
||||||
}else{
|
}else{
|
||||||
pAccum->nChar -= n;
|
/* If removing single accumulated string, harmlessly over-do. */
|
||||||
memmove(pAccum->zText, &pAccum->zText[n], pAccum->nChar);
|
nVS += pGCC->nFirstSepLength;
|
||||||
|
}
|
||||||
|
if( nVS>=(int)pGCC->str.nChar ){
|
||||||
|
pGCC->str.nChar = 0;
|
||||||
|
}else{
|
||||||
|
pGCC->str.nChar -= nVS;
|
||||||
|
memmove(pGCC->str.zText, &pGCC->str.zText[nVS], pGCC->str.nChar);
|
||||||
|
}
|
||||||
|
if( pGCC->str.nChar==0 ){
|
||||||
|
pGCC->str.mxAlloc = 0;
|
||||||
|
sqlite3_free(pGCC->pnSepLengths);
|
||||||
|
pGCC->pnSepLengths = 0;
|
||||||
}
|
}
|
||||||
if( pAccum->nChar==0 ) pAccum->mxAlloc = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
# define groupConcatInverse 0
|
# define groupConcatInverse 0
|
||||||
#endif /* SQLITE_OMIT_WINDOWFUNC */
|
#endif /* SQLITE_OMIT_WINDOWFUNC */
|
||||||
static void groupConcatFinalize(sqlite3_context *context){
|
static void groupConcatFinalize(sqlite3_context *context){
|
||||||
StrAccum *pAccum;
|
GroupConcatCtx *pGCC
|
||||||
pAccum = sqlite3_aggregate_context(context, 0);
|
= (GroupConcatCtx*)sqlite3_aggregate_context(context, 0);
|
||||||
if( pAccum ){
|
if( pGCC ){
|
||||||
|
StrAccum *pAccum = &pGCC->str;
|
||||||
if( pAccum->accError==SQLITE_TOOBIG ){
|
if( pAccum->accError==SQLITE_TOOBIG ){
|
||||||
sqlite3_result_error_toobig(context);
|
sqlite3_result_error_toobig(context);
|
||||||
}else if( pAccum->accError==SQLITE_NOMEM ){
|
}else if( pAccum->accError==SQLITE_NOMEM ){
|
||||||
sqlite3_result_error_nomem(context);
|
sqlite3_result_error_nomem(context);
|
||||||
}else{
|
}else{
|
||||||
sqlite3_result_text(context, sqlite3StrAccumFinish(pAccum), -1,
|
int n = pAccum->nChar;
|
||||||
|
sqlite3_result_text(context, sqlite3StrAccumFinish(pAccum), n,
|
||||||
sqlite3_free);
|
sqlite3_free);
|
||||||
}
|
}
|
||||||
|
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||||
|
sqlite3_free(pGCC->pnSepLengths);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifndef SQLITE_OMIT_WINDOWFUNC
|
#ifndef SQLITE_OMIT_WINDOWFUNC
|
||||||
static void groupConcatValue(sqlite3_context *context){
|
static void groupConcatValue(sqlite3_context *context){
|
||||||
sqlite3_str *pAccum;
|
GroupConcatCtx *pGCC
|
||||||
pAccum = (sqlite3_str*)sqlite3_aggregate_context(context, 0);
|
= (GroupConcatCtx*)sqlite3_aggregate_context(context, 0);
|
||||||
if( pAccum ){
|
if( pGCC ){
|
||||||
|
StrAccum *pAccum = &pGCC->str;
|
||||||
if( pAccum->accError==SQLITE_TOOBIG ){
|
if( pAccum->accError==SQLITE_TOOBIG ){
|
||||||
sqlite3_result_error_toobig(context);
|
sqlite3_result_error_toobig(context);
|
||||||
}else if( pAccum->accError==SQLITE_NOMEM ){
|
}else if( pAccum->accError==SQLITE_NOMEM ){
|
||||||
sqlite3_result_error_nomem(context);
|
sqlite3_result_error_nomem(context);
|
||||||
}else{
|
}else{
|
||||||
const char *zText = sqlite3_str_value(pAccum);
|
const char *zText = sqlite3_str_value(pAccum);
|
||||||
sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT);
|
sqlite3_result_text(context, zText, pAccum->nChar, SQLITE_TRANSIENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/printf.c
14
src/printf.c
@@ -145,7 +145,7 @@ static char et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){
|
|||||||
/*
|
/*
|
||||||
** Set the StrAccum object to an error mode.
|
** Set the StrAccum object to an error mode.
|
||||||
*/
|
*/
|
||||||
static void setStrAccumError(StrAccum *p, u8 eError){
|
void sqlite3StrAccumSetError(StrAccum *p, u8 eError){
|
||||||
assert( eError==SQLITE_NOMEM || eError==SQLITE_TOOBIG );
|
assert( eError==SQLITE_NOMEM || eError==SQLITE_TOOBIG );
|
||||||
p->accError = eError;
|
p->accError = eError;
|
||||||
if( p->mxAlloc ) sqlite3_str_reset(p);
|
if( p->mxAlloc ) sqlite3_str_reset(p);
|
||||||
@@ -181,12 +181,12 @@ static char *printfTempBuf(sqlite3_str *pAccum, sqlite3_int64 n){
|
|||||||
char *z;
|
char *z;
|
||||||
if( pAccum->accError ) return 0;
|
if( pAccum->accError ) return 0;
|
||||||
if( n>pAccum->nAlloc && n>pAccum->mxAlloc ){
|
if( n>pAccum->nAlloc && n>pAccum->mxAlloc ){
|
||||||
setStrAccumError(pAccum, SQLITE_TOOBIG);
|
sqlite3StrAccumSetError(pAccum, SQLITE_TOOBIG);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
z = sqlite3DbMallocRaw(pAccum->db, n);
|
z = sqlite3DbMallocRaw(pAccum->db, n);
|
||||||
if( z==0 ){
|
if( z==0 ){
|
||||||
setStrAccumError(pAccum, SQLITE_NOMEM);
|
sqlite3StrAccumSetError(pAccum, SQLITE_NOMEM);
|
||||||
}
|
}
|
||||||
return z;
|
return z;
|
||||||
}
|
}
|
||||||
@@ -925,7 +925,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if( p->mxAlloc==0 ){
|
if( p->mxAlloc==0 ){
|
||||||
setStrAccumError(p, SQLITE_TOOBIG);
|
sqlite3StrAccumSetError(p, SQLITE_TOOBIG);
|
||||||
return p->nAlloc - p->nChar - 1;
|
return p->nAlloc - p->nChar - 1;
|
||||||
}else{
|
}else{
|
||||||
char *zOld = isMalloced(p) ? p->zText : 0;
|
char *zOld = isMalloced(p) ? p->zText : 0;
|
||||||
@@ -938,7 +938,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){
|
|||||||
}
|
}
|
||||||
if( szNew > p->mxAlloc ){
|
if( szNew > p->mxAlloc ){
|
||||||
sqlite3_str_reset(p);
|
sqlite3_str_reset(p);
|
||||||
setStrAccumError(p, SQLITE_TOOBIG);
|
sqlite3StrAccumSetError(p, SQLITE_TOOBIG);
|
||||||
return 0;
|
return 0;
|
||||||
}else{
|
}else{
|
||||||
p->nAlloc = (int)szNew;
|
p->nAlloc = (int)szNew;
|
||||||
@@ -956,7 +956,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){
|
|||||||
p->printfFlags |= SQLITE_PRINTF_MALLOCED;
|
p->printfFlags |= SQLITE_PRINTF_MALLOCED;
|
||||||
}else{
|
}else{
|
||||||
sqlite3_str_reset(p);
|
sqlite3_str_reset(p);
|
||||||
setStrAccumError(p, SQLITE_NOMEM);
|
sqlite3StrAccumSetError(p, SQLITE_NOMEM);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1029,7 +1029,7 @@ static SQLITE_NOINLINE char *strAccumFinishRealloc(StrAccum *p){
|
|||||||
memcpy(zText, p->zText, p->nChar+1);
|
memcpy(zText, p->zText, p->nChar+1);
|
||||||
p->printfFlags |= SQLITE_PRINTF_MALLOCED;
|
p->printfFlags |= SQLITE_PRINTF_MALLOCED;
|
||||||
}else{
|
}else{
|
||||||
setStrAccumError(p, SQLITE_NOMEM);
|
sqlite3StrAccumSetError(p, SQLITE_NOMEM);
|
||||||
}
|
}
|
||||||
p->zText = zText;
|
p->zText = zText;
|
||||||
return zText;
|
return zText;
|
||||||
|
|||||||
@@ -4940,6 +4940,7 @@ int sqlite3OpenTempDatabase(Parse *);
|
|||||||
|
|
||||||
void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int);
|
void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int);
|
||||||
char *sqlite3StrAccumFinish(StrAccum*);
|
char *sqlite3StrAccumFinish(StrAccum*);
|
||||||
|
void sqlite3StrAccumSetError(StrAccum*, u8);
|
||||||
void sqlite3SelectDestInit(SelectDest*,int,int);
|
void sqlite3SelectDestInit(SelectDest*,int,int);
|
||||||
Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int);
|
Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#
|
#
|
||||||
#***********************************************************************
|
#***********************************************************************
|
||||||
# Test cases for RANGE BETWEEN and especially with NULLS LAST
|
# Test cases for RANGE BETWEEN and especially with NULLS LAST
|
||||||
|
# and for varying separator handling by group_concat().
|
||||||
#
|
#
|
||||||
|
|
||||||
set testdir [file dirname $argv0]
|
set testdir [file dirname $argv0]
|
||||||
@@ -356,5 +357,12 @@ do_execsql_test 8.1 {
|
|||||||
FROM t1;
|
FROM t1;
|
||||||
} {111 660 938 979}
|
} {111 660 938 979}
|
||||||
|
|
||||||
|
do_execsql_test 9.0 {
|
||||||
|
CREATE TABLE seps(x);
|
||||||
|
INSERT INTO seps(x) VALUES ('1'), ('22'), ('333'), ('4444');
|
||||||
|
SELECT group_concat('-', x)
|
||||||
|
OVER ( ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING )
|
||||||
|
FROM seps;
|
||||||
|
} {-22- -22-333- -333-4444- -4444-}
|
||||||
|
|
||||||
finish_test
|
finish_test
|
||||||
|
|||||||
66
test/windowC.test
Normal file
66
test/windowC.test
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# 2021-09-29
|
||||||
|
#
|
||||||
|
# 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 varying separator handling by group_concat().
|
||||||
|
#
|
||||||
|
|
||||||
|
set testdir [file dirname $argv0]
|
||||||
|
source $testdir/tester.tcl
|
||||||
|
set testprefix windowB
|
||||||
|
|
||||||
|
ifcapable !windowfunc {
|
||||||
|
finish_test
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do_execsql_test 1.0 {
|
||||||
|
CREATE TABLE x1(i INTEGER PRIMARY KEY, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach {tn bBlob seps} {
|
||||||
|
1 0 {a b c def g}
|
||||||
|
2 0 {abcdefg {} {} abcdefg}
|
||||||
|
3 0 {a bc def ghij klmno pqrstu}
|
||||||
|
4 1 {a bc def ghij klmno pqrstu}
|
||||||
|
5 1 {, , , , , , , , , , , , ....... , ,}
|
||||||
|
} {
|
||||||
|
foreach type {text blob} {
|
||||||
|
do_test 1.$type.$tn.1 {
|
||||||
|
execsql { DELETE FROM x1 }
|
||||||
|
foreach s $seps {
|
||||||
|
if {$type=="text"} {
|
||||||
|
execsql {INSERT INTO x1 VALUES(NULL, $s)}
|
||||||
|
} else {
|
||||||
|
execsql {INSERT INTO x1 VALUES(NULL, CAST ($s AS blob))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} {}
|
||||||
|
|
||||||
|
foreach {tn2 win} {
|
||||||
|
1 "ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"
|
||||||
|
2 "ROWS BETWEEN 2 PRECEDING AND CURRENT ROW"
|
||||||
|
3 "ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING"
|
||||||
|
} {
|
||||||
|
do_test 1.$type.$tn.2.$tn2 {
|
||||||
|
db eval "
|
||||||
|
SELECT group_concat('val', x) OVER ( ORDER BY i $win ) AS val FROM x1
|
||||||
|
" {
|
||||||
|
if {[string range $val 0 2]!="val"
|
||||||
|
|| [string range $val end-2 end]!="val"
|
||||||
|
} {
|
||||||
|
error "unexpected return value: $val"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_test
|
||||||
@@ -292,4 +292,43 @@ do_faultsim_test 12 -faults oom* -prep {
|
|||||||
faultsim_test_result {0 {}}
|
faultsim_test_result {0 {}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
reset_db
|
||||||
|
do_execsql_test 13.0 {
|
||||||
|
CREATE TABLE t1(id INTEGER PRIMARY KEY, a, b);
|
||||||
|
INSERT INTO t1 VALUES(1, '1', 'a');
|
||||||
|
INSERT INTO t1 VALUES(2, '22', 'b');
|
||||||
|
INSERT INTO t1 VALUES(3, '333', 'c');
|
||||||
|
INSERT INTO t1 VALUES(4, '4444', 'dddd');
|
||||||
|
INSERT INTO t1 VALUES(5, '55555', 'e');
|
||||||
|
INSERT INTO t1 VALUES(6, '666666', 'f');
|
||||||
|
INSERT INTO t1 VALUES(7, '7777777', 'gggggggggg');
|
||||||
|
} {}
|
||||||
|
|
||||||
|
set queryres [list {*}{
|
||||||
|
1b22
|
||||||
|
1b22c333
|
||||||
|
22c333dddd4444
|
||||||
|
333dddd4444e55555
|
||||||
|
4444e55555f666666
|
||||||
|
55555f666666gggggggggg7777777
|
||||||
|
666666gggggggggg7777777
|
||||||
|
}]
|
||||||
|
do_execsql_test 13.1 {
|
||||||
|
SELECT group_concat(a, b) OVER (
|
||||||
|
ORDER BY id RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING
|
||||||
|
) FROM t1
|
||||||
|
} $queryres
|
||||||
|
|
||||||
|
do_faultsim_test 13 -faults oom* -prep {
|
||||||
|
} -body {
|
||||||
|
execsql {
|
||||||
|
SELECT group_concat(a, b) OVER (
|
||||||
|
ORDER BY id RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING
|
||||||
|
) FROM t1
|
||||||
|
}
|
||||||
|
} -test {
|
||||||
|
faultsim_test_result [list 0 $::queryres]
|
||||||
|
}
|
||||||
|
|
||||||
finish_test
|
finish_test
|
||||||
|
|||||||
Reference in New Issue
Block a user