]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Implemenation of experimental API sqlite3_schema_copy(), for copying a database schem...
authordan <Dan Kennedy>
Mon, 6 Jan 2025 20:39:57 +0000 (20:39 +0000)
committerdan <Dan Kennedy>
Mon, 6 Jan 2025 20:39:57 +0000 (20:39 +0000)
FossilOrigin-Name: 65ede04d2176e7206ca6ac004df14f488c274a6b092f6a7dc897b049012898fb

manifest
manifest.uuid
src/alter.c
src/analyze.c
src/build.c
src/sqlite.h.in
src/sqliteInt.h
src/test1.c
src/trigger.c
test/schemacopy.test [new file with mode: 0644]

index 4daa047e6997bab3466acefbd360741c1129fe34..0c8916618bc63d714ff6b6df79b0d47ea8bde7a6 100644 (file)
--- 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.
index 4f33e411fea5590a88fa749145b5114bba631ef0..71e458f44a7fe63513a671d9ccef20a47337caff 100644 (file)
@@ -1 +1 @@
-dc3a24a784c95398656e0d7885f7eb0ee626b86c896e759a6ac5c243fd6f0ab7
+65ede04d2176e7206ca6ac004df14f488c274a6b092f6a7dc897b049012898fb
index ff20757589b37b046aeb2176df083aed4021e9e3..b087c5f0b7c559e861e1e5574918b6f074afb0c5 100644 (file)
@@ -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 */
       }
index 58f29ef717081326be1d6dd7a1766827ed45cdb1..b8f1f70d8ddf7aec46d4ad67707917cfba14b0fc 100644 (file)
@@ -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; ii<pTo->nSample; 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);
       }
index 2732ebb7314d61c90620eb80375d45129160a52c..7d4709e6b2f104f2f6bd98e47a0f597d5a66b93e 100644 (file)
@@ -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; ii<p->nColumn; 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; ii<p->nColumn; 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; ii<pSrc->nSrc; 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; ii<p->nCol; 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; ii<p->nCol; 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; ii<pNew->nCol; 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; ii<pNew->u.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) */
index fed9295587dca6e4f1c8f6074783277cc7efbb2f..c3b77319b330cef9980b13138cf360392ae08b07 100644 (file)
@@ -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
index 19be90f86f85906951c4b74e981a74965651a11a..e3fc83b9427fac6cad9e4f26928b4c4c55438a8b 100644 (file)
@@ -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
 */
index 05af583556657ca16bdf2c241cb1ddb261e5e556..5d7d4747385389306b2576c8742fe0f22ccc5daa 100644 (file)
@@ -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);
index e306a2e66479f994b8a9fb05ac9e433c0cec7076..0c98faae7ed3b906642793d1acd3b5ad53d3c92c 100644 (file)
@@ -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 (file)
index 0000000..9f43fe7
--- /dev/null
@@ -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