From: dan Date: Mon, 6 Jan 2025 20:39:57 +0000 (+0000) Subject: Implemenation of experimental API sqlite3_schema_copy(), for copying a database schem... X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=132570c540f85af4089fa9bdbb52fa56782ea7d6;p=thirdparty%2Fsqlite.git Implemenation of experimental API sqlite3_schema_copy(), for copying a database schema between connections. More testing to come. FossilOrigin-Name: 65ede04d2176e7206ca6ac004df14f488c274a6b092f6a7dc897b049012898fb --- diff --git a/manifest b/manifest index 4daa047e69..0c8916618b 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Up\slogs\sto\s"v=21". -D 2024-12-23T11:31:16.861 +C Implemenation\sof\sexperimental\sAPI\ssqlite3_schema_copy(),\sfor\scopying\sa\sdatabase\sschema\sbetween\sconnections.\sMore\stesting\sto\scome. +D 2025-01-06T20:39:57.366 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -706,8 +706,8 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 acdff36db796e2d00225b911d3047d580cd136547298435426ce9d40347973cc F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2 -F src/alter.c aa93e37e4a36a0525bbb2a2aeda20d2018f0aa995542c7dc658e031375e3f532 -F src/analyze.c c011b69c903197f45688799d81e9f216be84f6eb4b3faa23a3d7638111859da2 +F src/alter.c c48e7cbb7f87b28e9e954bfed3327d8e1b8e2a020fbb5bbeca78f6534d6c3c31 +F src/analyze.c 19a2b6ed803a3c0add5f48adbac4735a064d329aa91790b94c968fef0bf7c438 F src/attach.c 08235ab62ed5ccc93c22bf36e640d19effcd632319615851bccf724ec9341333 F src/auth.c 4c1ea890e0069ad73bead5d17a5b12c34cfa4f1a24175c8147ea439b64be271c F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 @@ -716,7 +716,7 @@ F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 F src/btree.c 2664c81f217a42afadc7c010bb4a175057d5e53b99e9512234eb74817f2ad59c F src/btree.h bdeeb35614caa33526b603138f04c8d07a3f90a1300b5ade76848b755edf2027 F src/btreeInt.h caa893e74d2261fb0ff1681fce998533c0552858e882bd04fc6805075f5f6e75 -F src/build.c 08697d6a4df78f8e289582eb58473445492319676f81cc4794ef4056d36ae5fd +F src/build.c 19d6ba76fb72f81fed5323298284d56f0c32c7a59c38532e56c9811cdd9a89a5 F src/callback.c 43c8ca52b1ecbdec43522f121126fd4e3ee10bc9ca01cdd3ae207cfa419780b6 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 193f6f9a75204274b7e7f45ac6d6517c12c70b55a5dfb39312dfc3a52e2a8138 @@ -776,16 +776,16 @@ F src/resolve.c 2c127880c0634962837f16f2f48a295e514357af959330cc038de73015d5b5e8 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c d07d1872161db7c922194c5279d67686e8355c6d304ed0e0646fbaa59b59f561 F src/shell.c.in 40de636c1d90fb8a9ca7f49dc8f50d930f1b60736e73aca5eb37c4c7d0e47f9d -F src/sqlite.h.in b7ff496637807ae88b2557039fc940518db328bf5d5621e2f7c048dfba32a52b +F src/sqlite.h.in f991accd827e7853d092366690772b2de831a8fcbe1cb127f3831e39a4302e7e F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54 -F src/sqliteInt.h 4281b9ed8b660fe703a58ff8aa70247df07ad6b75f47eb7f45d1380021720f81 +F src/sqliteInt.h 9e7a2d0941b10f059c1c62f13cea460b55bcd6b68c3a3c2fe170ce79e25a0032 F src/sqliteLimit.h da2cffdffa7d71b035f9e59668fbaad74b5939300dbfa9915304e6d8f72b0761 F src/status.c cb11f8589a6912af2da3bb1ec509a94dd8ef27df4d4c1a97e0bcf2309ece972b F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/tclsqlite.c c6888598f08dee3d9112a38ef42c8f5c89ca7f3190f4694744d0b84250f4bf8c F src/tclsqlite.h c6af51f31a2b2172d674608763a4b98fdf5cd587e4025053e546fb8077757262 -F src/test1.c ecbd27140e63d6d70e221ac7e2aa565a13359c126f432b4469ad80f5d66d62ee +F src/test1.c 10d600bdbd99b4fa078195ca7d4746880f61553196a341ac7f1c246c1e882f24 F src/test2.c 7ebc518e6735939d8979273a6f7b1d9b5702babf059f6ad62499f7f60a9eb9a3 F src/test3.c e7573aa0f78ee4e070a4bc8c3493941c1aa64d5c66d4825c74c0f055451f432b F src/test4.c 13e57ae7ec7a959ee180970aef09deed141252fe9bb07c61054f0dfa4f1dfd5d @@ -839,7 +839,7 @@ F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/tokenize.c 3f703cacdab728d7741e5a6ac242006d74fe1c2754d4f03ed889d7253259bd68 F src/treeview.c 88aa39b754f5ef7214385c1bbbdd2f3dc20efafeed0cf590e8d1199b9c6e44aa -F src/trigger.c 247e2d712d5edc6021d52a169f6ac9a9c10d7144bc4ac7ea06c1ed2aa414659f +F src/trigger.c 6868181a0efd15ddaf82f23658b47eda4a5fa5bee49fbf94ccf8abe383559a32 F src/update.c 2dd1b745acc9253df1b210ac69137c7a6b290e561d3f42da24418c4e807e889b F src/upsert.c 215328c3f91623c520ec8672c44323553f12caeb4f01b1090ebdca99fdf7b4f1 F src/utf.c f23165685a67b4caf8ec08fb274cb3f319103decfb2a980b7cfd55d18dfa855e @@ -1610,6 +1610,7 @@ F test/schema3.test 8ed4ae66e082cdd8b1b1f22d8549e1e7a0db4527a8e6ee8b6193053ee1e5 F test/schema4.test 3b26c9fa916abb6dadf894137adcf41b7796f7b9 F test/schema5.test 29699b4421f183c8f0e88bd28ce7d75d13ea653e F test/schema6.test e4bd1f23d368695eb9e7b51ef6e02ca0642ea2ab4a52579959826b5e7dce1f9b +F test/schemacopy.test 63091fa6dd23a5179a789882f2932dd00159225e313c13b3ffa6698e846dcebd F test/schemafault.test 1936bceca55ac82c5efbcc9fc91a1933e45c8d1e1d106b9a7e56c972a5a2a51e F test/securedel.test 2f70b2449186a1921bd01ec9da407fbfa98c3a7a5521854c300c194b2ff09384 F test/securedel2.test 2d54c28e46eb1fd6902089958b20b1b056c6f1c5 @@ -2250,8 +2251,11 @@ F vsixtest/vsixtest.tcl 6195aba1f12a5e10efc2b8c0009532167be5e301abe5b31385638080 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 6e800b7035f55a211917d28cacf829b1681f37cbd2e6989c4cc20d4027a4192d -R 9198f76a7ca82655c38c2643038b3bc6 +P dc3a24a784c95398656e0d7885f7eb0ee626b86c896e759a6ac5c243fd6f0ab7 +R 319fabba106aeb0d0322166af764ce3c +T *branch * schema-copy +T *sym-schema-copy * +T -sym-cf8f1552-commit-instr * U dan -Z 7e52e40c5c5e6ec4cc1a91e31abd90cb +Z 463cbc692201d0ebdc4f4cfb189d8b05 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 4f33e411fe..71e458f44a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -dc3a24a784c95398656e0d7885f7eb0ee626b86c896e759a6ac5c243fd6f0ab7 +65ede04d2176e7206ca6ac004df14f488c274a6b092f6a7dc897b049012898fb diff --git a/src/alter.c b/src/alter.c index ff20757589..b087c5f0b7 100644 --- a/src/alter.c +++ b/src/alter.c @@ -1416,7 +1416,7 @@ static int renameResolveTrigger(Parse *pParse){ ** Invoke sqlite3WalkExpr() or sqlite3WalkSelect() on all Select or Expr ** objects that are part of the trigger passed as the second argument. */ -static void renameWalkTrigger(Walker *pWalker, Trigger *pTrigger){ +void sqlite3WalkTrigger(Walker *pWalker, Trigger *pTrigger){ TriggerStep *pStep; /* Find tokens to edit in WHEN clause */ @@ -1631,7 +1631,7 @@ static void renameColumnFunc( } /* Find tokens to edit in various expressions and selects */ - renameWalkTrigger(&sWalker, sParse.pNewTrigger); + sqlite3WalkTrigger(&sWalker, sParse.pNewTrigger); } assert( rc==SQLITE_OK ); @@ -1824,7 +1824,7 @@ static void renameTableFunc( if( isLegacy==0 ){ rc = renameResolveTrigger(&sParse); if( rc==SQLITE_OK ){ - renameWalkTrigger(&sWalker, pTrigger); + sqlite3WalkTrigger(&sWalker, pTrigger); for(pStep=pTrigger->step_list; pStep; pStep=pStep->pNext){ if( pStep->zTarget && 0==sqlite3_stricmp(pStep->zTarget, zOld) ){ renameTokenFind(&sParse, &sCtx, pStep->zTarget); @@ -1965,7 +1965,7 @@ static void renameQuotefixFunc( #ifndef SQLITE_OMIT_TRIGGER rc = renameResolveTrigger(&sParse); if( rc==SQLITE_OK ){ - renameWalkTrigger(&sWalker, sParse.pNewTrigger); + sqlite3WalkTrigger(&sWalker, sParse.pNewTrigger); } #endif /* SQLITE_OMIT_TRIGGER */ } diff --git a/src/analyze.c b/src/analyze.c index 58f29ef717..b8f1f70d8d 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -1778,7 +1778,7 @@ static Index *findIndexOrPrimaryKey( ** Grow the pIdx->aSample[] array. Return SQLITE_OK if successful, or ** SQLITE_NOMEM otherwise. */ -static int growSampleArray(sqlite3 *db, Index *pIdx, int *piOff){ +static int growSampleArray(sqlite3 *db, Index *pIdx, int nReq, int *piOff){ int nIdxCol = pIdx->nSampleCol; int nNew = 0; IndexSample *aNew = 0; @@ -1789,9 +1789,13 @@ static int growSampleArray(sqlite3 *db, Index *pIdx, int *piOff){ u64 t; assert( pIdx->nSample==pIdx->nSampleAlloc ); - nNew = SQLITE_STAT4_EST_SAMPLES; - if( pIdx->nSample ){ - nNew = pIdx->nSample*2; + if( nReq==0 ){ + nNew = SQLITE_STAT4_EST_SAMPLES; + if( pIdx->nSample ){ + nNew = pIdx->nSample*2; + } + }else{ + nNew = nReq; } /* Set nByte to the required amount of space */ @@ -1840,6 +1844,32 @@ static int growSampleArray(sqlite3 *db, Index *pIdx, int *piOff){ return SQLITE_OK; } +int sqlite3AnalyzeCopyStat4( + sqlite3 *db, + Index *pTo, + Index *pFrom +){ + if( pFrom->nSample>0 ){ + Schema *pSchema = pTo->pSchema; + int ii; + + pTo->nSample = pTo->nSampleAlloc = 0; + if( growSampleArray(db, pTo, pFrom->nSample, &pSchema->nStat4Space) ){ + return SQLITE_NOMEM; + } + pTo->nSample = pFrom->nSample; + memcpy(pTo->aAvgEq, pFrom->aAvgEq, pFrom->nSampleCol * sizeof(tRowcnt)); + memcpy(pTo->aSample[0].anEq, pFrom->aSample[0].anEq, + pTo->nSampleCol * 3 * sizeof(tRowcnt) * pTo->nSample + ); + for(ii=0; iinSample; ii++){ + pTo->aSample[ii].p = pFrom->aSample[ii].p; + pTo->aSample[ii].n = pFrom->aSample[ii].n; + } + } + return SQLITE_OK; +} + /* ** Allocate the space that will likely be required for the Index.aSample[] ** arrays populated by loading data from the sqlite_stat4 table. Return @@ -1871,6 +1901,7 @@ static int stat4AllocSpace(sqlite3 *db, const char *zDb){ if( pSchema->pStat4Space==0 ){ return SQLITE_NOMEM_BKPT; } + pSchema->nStat4Space = nByte; } return SQLITE_OK; @@ -1941,7 +1972,7 @@ static int loadStatTbl( pIdx->nSampleCol = pIdx->nColumn; } t2 = sqlite3STimeNow(); - if( growSampleArray(db, pIdx, &iBlockOff) ) break; + if( growSampleArray(db, pIdx, 0, &iBlockOff) ) break; if( db->aSchemaTime ){ db->aSchemaTime[SCHEMA_TIME_STAT4_GROWUS] += (sqlite3STimeNow() - t); } diff --git a/src/build.c b/src/build.c index 2732ebb731..7d4709e6b2 100644 --- a/src/build.c +++ b/src/build.c @@ -5795,4 +5795,349 @@ void sqlite3WithDelete(sqlite3 *db, With *pWith){ void sqlite3WithDeleteGeneric(sqlite3 *db, void *pWith){ sqlite3WithDelete(db, (With*)pWith); } + +static Index *schemaCopyIndexList(sqlite3 *db, Table *pTab, Index *pIdx){ + Schema *pSchema = pTab->pSchema; + Index *pRet = 0; + Index *p = 0; + Index **ppNew = &pRet; + for(p=pIdx; p; p=p->pNext){ + Index *pNew = 0; + int nName = sqlite3Strlen30(p->zName) + 1; + int nExtra = 0; + char *zExtra = 0; + int ii; + for(ii=0; iinColumn; ii++){ + if( p->azColl[ii]!=sqlite3StrBINARY ){ + nExtra += sqlite3Strlen30(p->azColl[ii]) + 1; + } + } + + pNew = sqlite3AllocateIndexObject(db, p->nColumn, nName+nExtra, &zExtra); + if( pNew ){ + pNew->zName = zExtra; + memcpy(pNew->zName, p->zName, nName); + zExtra += nName; + memcpy(pNew->aiColumn, p->aiColumn, sizeof(i16) * p->nColumn); + memcpy(pNew->aiRowLogEst, p->aiRowLogEst, sizeof(LogEst)*(p->nColumn+1)); + pNew->pTable = pTab; + pNew->zColAff = 0; + pNew->pSchema = pSchema; + memcpy(pNew->aSortOrder, p->aSortOrder, p->nColumn); + for(ii=0; iinColumn; ii++){ + char *zColl = 0; + if( p->azColl[ii]!=sqlite3StrBINARY ){ + int nColl = sqlite3Strlen30(p->azColl[ii]) + 1; + memcpy(zExtra, p->azColl[ii], nColl); + zColl = zExtra; + zExtra += nColl; + }else{ + zColl = (char*)sqlite3StrBINARY; + } + pNew->azColl[ii] = zColl; + } + pNew->pPartIdxWhere = sqlite3ExprDup(db, p->pPartIdxWhere, 0); + pNew->aColExpr = sqlite3ExprListDup(db, p->aColExpr, 0); + pNew->tnum = p->tnum; + pNew->szIdxRow = p->szIdxRow; + memcpy(&pNew->onError,&p->onError,sizeof(Index)-offsetof(Index, onError)); + pNew->isResized = 0; +#ifdef SQLITE_ENABLE_STAT4 + assert( pNew->aiRowEst==0 && p->aiRowEst==0 ); + sqlite3AnalyzeCopyStat4(db, pNew, p); +#endif + + if( sqlite3HashInsert(&pSchema->idxHash, pNew->zName, pNew) ){ + sqlite3OomFault(db); + } + *ppNew = pNew; + ppNew = &pNew->pNext; + } + } + + return pRet; +} + +static void schemaCopyRefixSrclist(Schema *pSchema, SrcList *pSrc){ + if( pSrc ){ + int ii; + for(ii=0; iinSrc; ii++){ + if( pSrc->a[ii].fg.fixedSchema ){ + pSrc->a[ii].u4.pSchema = pSchema; + } + } + } +} + +static int schemaCopySelectCb(Walker *pWalker, Select *pSelect){ + schemaCopyRefixSrclist(pWalker->u.pSchema, pSelect->pSrc); + return WRC_Continue; +} + +static TriggerStep *schemaCopyTriggerStepList( + sqlite3 *db, + Trigger *pTrig, + TriggerStep *pList +){ + TriggerStep *pRet = 0; + TriggerStep *p = 0; + TriggerStep **ppNew = &pRet; + for(p=pList; p; p=p->pNext){ + int nTarget = sqlite3Strlen30(p->zTarget) + 1; + int nAlloc = sizeof(TriggerStep) + nTarget; + TriggerStep *pNew = (TriggerStep*)sqlite3DbMallocZero(db, nAlloc); + if( pNew ){ + pNew->op = p->op; + pNew->orconf = p->orconf; + pNew->pTrig = pTrig; + pNew->pSelect = sqlite3SelectDup(db, p->pSelect, 0); + if( p->zTarget ){ + pNew->zTarget = (char*)&pNew[1]; + memcpy(pNew->zTarget, p->zTarget, nTarget); + } + pNew->pFrom = sqlite3SrcListDup(db, p->pFrom, 0); + schemaCopyRefixSrclist(pTrig->pSchema, pNew->pFrom); + pNew->pWhere = sqlite3ExprDup(db, p->pWhere, 0); + pNew->pExprList = sqlite3ExprListDup(db, p->pExprList, 0); + pNew->pIdList = sqlite3IdListDup(db, p->pIdList); + pNew->pUpsert = sqlite3UpsertDup(db, p->pUpsert); + assert( pNew->pUpsert==0 || pNew->pUpsert->pUpsertSrc==0 ); + pNew->zSpan = sqlite3DbStrDup(db, p->zSpan); + + *ppNew = pNew; + ppNew = &pNew->pNext; + if( pRet ){ + pRet->pLast = pNew; + } + } + } + return pRet; +} + +static Trigger *schemaCopyTriggerList(sqlite3 *db, Table *pTab, Trigger *pList){ + Walker sWalker; + Schema *pSchema = pTab->pSchema; + Trigger *pRet = 0; + Trigger *p = 0; + Trigger **ppNew = &pRet; + + memset(&sWalker, 0, sizeof(sWalker)); + sWalker.xSelectCallback = schemaCopySelectCb; + sWalker.xExprCallback = sqlite3ExprWalkNoop; + sWalker.u.pSchema = pSchema; + + for(p=pList; p; p=p->pNext){ + Trigger *pNew = sqlite3DbMallocZero(db, sizeof(Trigger)); + if( pNew ){ + memcpy(pNew, p, sizeof(Trigger)); + pNew->zName = sqlite3DbStrDup(db, pNew->zName); + pNew->table = sqlite3DbStrDup(db, pNew->table); + pNew->pWhen = sqlite3ExprDup(db, pNew->pWhen, 0); + pNew->pColumns = sqlite3IdListDup(db, pNew->pColumns); + pNew->pSchema = pTab->pSchema; + pNew->pTabSchema = pTab->pSchema; + pNew->step_list = schemaCopyTriggerStepList(db, pNew, pNew->step_list); + if( sqlite3HashInsert(&pSchema->trigHash, pNew->zName, pNew) ){ + sqlite3OomFault(db); + } + sqlite3WalkTrigger(&sWalker, pNew); + *ppNew = pNew; + ppNew = &pNew->pNext; + } + } + + return pRet; +} + +static FKey *schemaCopyFKeyList(sqlite3 *db, Table *pTab, FKey *pList){ + Schema *pSchema = pTab->pSchema; + FKey *pRet = 0; + FKey *p = 0; + FKey **ppNew = &pRet; + for(p=pList; p; p=p->pNextFrom){ + FKey *pNew = 0; + int nByte = sizeof(FKey) + ((p->nCol - 1) * sizeof(struct sColMap)); + int ii; + nByte += sqlite3Strlen30(p->zTo) + 1; + for(ii=0; iinCol; ii++){ + nByte += sqlite3Strlen30(p->aCol[ii].zCol) + 1; + } + pNew = (FKey*)sqlite3DbMallocZero(db, nByte); + if( pNew ){ + FKey *pNextTo = 0; + char *z = (char*)&pNew->aCol[p->nCol]; + int n = 0; + pNew->pFrom = pTab; + n = sqlite3Strlen30(p->zTo) + 1; + pNew->zTo = z; + memcpy(pNew->zTo, p->zTo, n); + z += n; + pNew->nCol = p->nCol; + pNew->isDeferred = p->isDeferred; + pNew->aAction[0] = p->aAction[0]; + pNew->aAction[1] = p->aAction[1]; + for(ii=0; iinCol; ii++){ + pNew->aCol[ii].iFrom = p->aCol[ii].iFrom; + if( p->aCol[ii].zCol ){ + n = sqlite3Strlen30(p->aCol[ii].zCol) + 1; + pNew->aCol[ii].zCol = z; + memcpy(z, p->aCol[ii].zCol, n); + z += n; + } + } + + pNextTo = (FKey*)sqlite3HashInsert(&pSchema->fkeyHash, pNew->zTo, pNew); + if( pNextTo==pNew ){ + sqlite3OomFault(db); + }else if( pNextTo ){ + assert( pNextTo->pPrevTo==0 ); + pNew->pNextTo = pNextTo; + pNextTo->pPrevTo = pNew; + } + + *ppNew = pNew; + ppNew = &pNew->pNextFrom; + } + } + + return pRet; +} + +static void schemaCopyTable(sqlite3 *db, Schema *pTo, Table *pTab){ + Table *pNew = 0; + + pNew = (Table*)sqlite3DbMallocRawNN(db, sizeof(Table)); + if( pNew ){ + memcpy(pNew, pTab, sizeof(Table)); + pNew->zName = sqlite3DbStrDup(db, pNew->zName); + pNew->aCol = sqlite3DbMallocRawNN(db, pNew->nCol*sizeof(Column)); + pNew->nTabRef = 1; + if( pNew->aCol ){ + int ii; + memcpy(pNew->aCol, pTab->aCol, pNew->nCol*sizeof(Column)); + for(ii=0; iinCol; ii++){ + Column *pCol = &pNew->aCol[ii]; + const char *zCopy = pCol->zCnName; + int nCopy = sqlite3Strlen30(zCopy); + if( pCol->colFlags & COLFLAG_HASTYPE ){ + nCopy++; + nCopy += sqlite3Strlen30(&zCopy[nCopy]); + } + if( pCol->colFlags & COLFLAG_HASCOLL ){ + nCopy++; + nCopy += sqlite3Strlen30(&zCopy[nCopy]); + } + pCol->zCnName = sqlite3DbStrNDup(db, zCopy, nCopy); + } + } + + pNew->pSchema = pTo; + pNew->pIndex = schemaCopyIndexList(db, pNew, pNew->pIndex); + pNew->zColAff = 0; + pNew->pCheck = sqlite3ExprListDup(db, pTab->pCheck, 0); + + if( IsView(pNew) ){ + Walker sWalker; + memset(&sWalker, 0, sizeof(sWalker)); + sWalker.xSelectCallback = schemaCopySelectCb; + sWalker.xExprCallback = sqlite3ExprWalkNoop; + sWalker.u.pSchema = pTo; + pNew->u.view.pSelect = sqlite3SelectDup(db, pNew->u.view.pSelect, 0); + sqlite3WalkSelect(&sWalker, pNew->u.view.pSelect); + }else if( IsVirtual(pNew) ){ + int nAlloc = pNew->u.vtab.nArg * sizeof(char*); + pNew->u.vtab.p = 0; + pNew->u.vtab.azArg = (char**)sqlite3DbMallocRaw(db, nAlloc); + if( pNew->u.vtab.azArg ){ + int ii; + for(ii=0; iiu.vtab.nArg; ii++){ + pNew->u.vtab.azArg[ii] = sqlite3DbStrDup(db, pTab->u.vtab.azArg[ii]); + } + } + }else{ + pNew->u.tab.pDfltList = sqlite3ExprListDup(db, pNew->u.tab.pDfltList, 0); + pNew->u.tab.pFKey = schemaCopyFKeyList(db, pNew, pNew->u.tab.pFKey); + } + + pNew->pTrigger = schemaCopyTriggerList(db, pNew, pNew->pTrigger); + } + + if( db->mallocFailed==0 ){ + if( sqlite3HashInsert(&pTo->tblHash, pNew->zName, pNew) ){ + db->mallocFailed = 1; + } +#ifndef SQLITE_OMIT_AUTOINCREMENT + if( strcmp(pNew->zName, "sqlite_sequence")==0 ){ + pTo->pSeqTab = pNew; + } +#endif + } + if( db->mallocFailed ){ + sqlite3DeleteTable(db, pNew); + } +} + +int sqlite3_schema_copy( + sqlite3 *db, const char *zTo, + sqlite3 *dbFrom, const char *zFrom +){ + int iTo = 0; + int iFrom = 0; + Schema *pTo = 0; + Schema *pFrom = 0; + HashElem *k = 0; + int rc = SQLITE_OK; + + sqlite3_mutex_enter(db->mutex); + sqlite3BtreeEnterAll(db); + DisableLookaside; + + if( zTo ) iTo = sqlite3FindDbName(db, zTo); + if( zFrom ) iFrom = sqlite3FindDbName(dbFrom, zFrom); + + if( iFrom<0 || iTo<0 ){ + rc = SQLITE_ERROR; + goto schema_copy_done; + } + + if( !DbHasProperty(dbFrom, iFrom, DB_SchemaLoaded) + || DbHasProperty(db, iTo, DB_SchemaLoaded) + ){ + goto schema_copy_done; + } + pTo = db->aDb[iTo].pSchema; + pFrom = dbFrom->aDb[iFrom].pSchema; + assert( pTo && pFrom ); + + pTo->schema_cookie = pFrom->schema_cookie; + pTo->iGeneration = pFrom->iGeneration; + pTo->file_format = pFrom->file_format; + pTo->enc = pFrom->enc; + pTo->cache_size = pFrom->cache_size; + pTo->schemaFlags = pFrom->schemaFlags; + +#ifdef SQLITE_ENABLE_STAT4 + if( pFrom->pStat4Space ){ + pTo->pStat4Space = sqlite3_malloc(pFrom->nStat4Space); + if( pTo->pStat4Space==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto schema_copy_done; + } + pTo->nStat4Space = 0; + } +#endif + + for(k=sqliteHashFirst(&pFrom->tblHash); k; k=sqliteHashNext(k)){ + Table *pTab = (Table*)sqliteHashData(k); + schemaCopyTable(db, pTo, pTab); + } + + schema_copy_done: + EnableLookaside; + sqlite3BtreeLeaveAll(db); + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + #endif /* !defined(SQLITE_OMIT_CTE) */ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index fed9295587..c3b77319b3 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -10952,6 +10952,10 @@ int sqlite3_commit_status( #define SQLITE_COMMIT_CONFLICT_FRAME 3 #define SQLITE_COMMIT_CONFLICT_PGNO 4 +int sqlite3_schema_copy( + sqlite3 *pTo, const char *zTo, + sqlite3 *pFrom, const char *zFrom +); /* ** Undo the hack that converts floating point types to integer for diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 19be90f86f..e3fc83b942 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1492,6 +1492,7 @@ struct Schema { int cache_size; /* Number of pages to use in the cache */ #ifdef SQLITE_ENABLE_STAT4 void *pStat4Space; /* Memory for stat4 Index.aSample[] arrays */ + int nStat4Space; /* Size of pStat4Space allocation in bytes */ #endif }; @@ -4470,6 +4471,7 @@ struct Walker { SrcItem *pSrcItem; /* A single FROM clause item */ DbFixer *pFix; /* See sqlite3FixSelect() */ Mem *aMem; /* See sqlite3BtreeCursorHint() */ + Schema *pSchema; } u; }; @@ -4953,6 +4955,7 @@ char *sqlite3VMPrintf(sqlite3*,const char*, va_list); void sqlite3ShowTriggerStepList(const TriggerStep*); void sqlite3ShowTrigger(const Trigger*); void sqlite3ShowTriggerList(const Trigger*); + void sqlite3WalkTrigger(Walker *pWalker, Trigger *pTrigger); #endif #ifndef SQLITE_OMIT_WINDOWFUNC void sqlite3ShowWindow(const Window*); @@ -5601,8 +5604,11 @@ int sqlite3Stat4ValueFromExpr(Parse*, Expr*, u8, sqlite3_value**); void sqlite3Stat4ProbeFree(UnpackedRecord*); int sqlite3Stat4Column(sqlite3*, const void*, int, int, sqlite3_value**); char sqlite3IndexColumnAffinity(sqlite3*, Index*, int); +int sqlite3AnalyzeCopyStat4(sqlite3*, Index*, Index *pFrom); #endif +TriggerStep *sqlite3SchemaCopyTriggerStepList(sqlite3 *, TriggerStep*); + /* ** The interface to the LEMON-generated parser */ diff --git a/src/test1.c b/src/test1.c index 05af583556..5d7d474738 100644 --- a/src/test1.c +++ b/src/test1.c @@ -2591,6 +2591,37 @@ static int SQLITE_TCLAPI test_sqlite3_randomness( return TCL_OK; } +/* +** Usage: sqlite3_schema_copy NBYTE +*/ +static int SQLITE_TCLAPI test_schema_copy( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3 *dbTo = 0; + sqlite3 *dbFrom = 0; + const char *zTo = 0; + const char *zFrom = 0; + int rc = SQLITE_OK; + + if( objc!=5 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DBTO DBNAMETO DBFROM DBFROMNAME"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &dbTo) ) return TCL_ERROR; + if( getDbPointer(interp, Tcl_GetString(objv[3]), &dbFrom) ) return TCL_ERROR; + zTo = Tcl_GetString(objv[2]); + zFrom = Tcl_GetString(objv[4]); + + rc = sqlite3_schema_copy(dbTo, zTo, dbFrom, zFrom); + + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, sqlite3ErrName(rc), 0); + return TCL_OK; +} + /* ** tclcmd: sqlite3_commit_status db DBNAME OP */ @@ -9412,6 +9443,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ #endif { "sqlite3_commit_status", test_commit_status, 0 }, { "sqlite3_randomness", test_sqlite3_randomness, 0 }, + { "sqlite3_schema_copy", test_schema_copy, 0 }, }; static int bitmask_size = sizeof(Bitmask)*8; static int longdouble_size = sizeof(LONGDOUBLE_TYPE); diff --git a/src/trigger.c b/src/trigger.c index e306a2e664..0c98faae7e 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -599,6 +599,7 @@ TriggerStep *sqlite3TriggerDeleteStep( return pTriggerStep; } + /* ** Recursively delete a Trigger structure */ diff --git a/test/schemacopy.test b/test/schemacopy.test new file mode 100644 index 0000000000..9f43fe7f3f --- /dev/null +++ b/test/schemacopy.test @@ -0,0 +1,131 @@ +# 2025 January 4 +# +# 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. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix schemacopy + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2), (3, 4); +} + +do_test 1.1 { + sqlite3 db2 test.db + sqlite3_schema_copy db2 main db main +} SQLITE_OK + +db close + +do_execsql_test -db db2 1.2 { + SELECT * FROM t1 +} {1 2 3 4} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 2.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE, c INTEGER, d); + CREATE INDEX i1 ON t1( (c+d) ); + CREATE INDEX i2 ON t1( (c+d) DESC, (d-c) ASC); + CREATE INDEX i3 ON t1( c, d ) WHERE b<3; + CREATE INDEX i4 ON t1( d COLLATE nocase); + CREATE INDEX i5 ON t1( c ); + + CREATE TABLE t2(x, y PRIMARY KEY, z) WITHOUT ROWID; + CREATE TABLE t3(x COLLATE nocase, y, z, PRIMARY KEY(x, y)) WITHOUT ROWID; + + CREATE UNIQUE INDEX t3i1 ON t3(z); + + CREATE TABLE t4(a, b, c); + CREATE TABLE log(x); + CREATE TRIGGER t4ai AFTER INSERT ON t4 BEGIN + INSERT INTO log VALUES(new.a ||','|| new.b ||','|| new.c); + UPDATE log SET x = new.rowid || ':' || x WHERE rowid = ( + SELECT max(rowid) FROM log + ); + END; + + CREATE TABLE x1(a INTEGER PRIMARY KEY, b); + CREATE TABLE x2(a INTEGER PRIMARY KEY, b); + CREATE TABLE x3(a INTEGER PRIMARY KEY, b); + + CREATE VIEW xx AS + SELECT a, b FROM x1 + UNION ALL + SELECT a, b FROM x2 + UNION ALL + SELECT a, b FROM x3; + SELECT * FROM xx; +} + +do_test 2.1 { + sqlite3 db2 test.db + sqlite3_schema_copy db2 main db main +} SQLITE_OK + +do_execsql_test -db db2 2.2 { + INSERT INTO t1 VALUES(1, 2, 3, 4); + INSERT INTO t1 VALUES(4, 3, 2, 1); + INSERT INTO t1 VALUES(NULL, 4, NULL, 'abc'); + INSERT INTO t1 VALUES(NULL, 5, NULL, 'AbC'); + INSERT INTO t1 VALUES(NULL, 6, NULL, 'DEF'); + INSERT INTO t1 VALUES(NULL, 7, NULL, 'def'); + INSERT INTO t1 VALUES(NULL, 8, '456', 11); + INSERT INTO t1 VALUES(NULL, 9, '016', 11); + + INSERT INTO t2 VALUES('a', 'b', 'c'); + + INSERT INTO t3 VALUES('abc', 'b', '1'); + INSERT INTO t3 VALUES('abc', 'c', '2'); + INSERT INTO t3 VALUES('def', 'a', '3'); + INSERT INTO t3 VALUES('DEF', 'b', '4'); + INSERT INTO t3 VALUES('aBc', 'd', '5'); + INSERT INTO t3 VALUES('ABC', 'e', '6'); +} + +do_execsql_test -db db2 2.3 { + SELECT * FROM t1 WHERE c='000016' +} {10 9 16 11} + +do_eqp_test 2.4 { + SELECT * FROM t1 WHERE c='000016' +} {i5} + +do_execsql_test -db db2 2.5.1 { + INSERT INTO t4 VALUES('a', 'b', 'c'); +} +do_execsql_test -db db2 2.5.2 { + INSERT INTO t4 VALUES(1, 2, 3); +} +do_execsql_test -db db2 2.5.3 { + SELECT * FROM log; +} { + 1:a,b,c + 2:1,2,3 +} + +do_execsql_test -db db2 2.6.1 { + INSERT INTO x1 VALUES(1, 'ii'); + INSERT INTO x2 VALUES(3, 'iv'); + INSERT INTO x3 VALUES(5, 'vi'); + SELECT * FROM xx; +} { + 1 ii 3 iv 5 vi +} + +do_execsql_test 2.integrity { + PRAGMA integrity_check +} {ok} + +finish_test