xref: /sqlite-3.40.0/ext/session/changeset.c (revision 065f3bf4)
13a67b045Sdrh /*
23a67b045Sdrh ** 2014-08-18
33a67b045Sdrh **
43a67b045Sdrh ** The author disclaims copyright to this source code.  In place of
53a67b045Sdrh ** a legal notice, here is a blessing:
63a67b045Sdrh **
73a67b045Sdrh **    May you do good and not evil.
83a67b045Sdrh **    May you find forgiveness for yourself and forgive others.
93a67b045Sdrh **    May you share freely, never taking more than you give.
103a67b045Sdrh **
113a67b045Sdrh *************************************************************************
123a67b045Sdrh ** This file contains code to implement the "changeset" command line
133a67b045Sdrh ** utility for displaying and transforming changesets generated by
143a67b045Sdrh ** the Sessions extension.
153a67b045Sdrh */
163a67b045Sdrh #include "sqlite3.h"
173a67b045Sdrh #include <stdio.h>
183a67b045Sdrh #include <stdlib.h>
193a67b045Sdrh #include <string.h>
203a67b045Sdrh #include <assert.h>
213a67b045Sdrh #include <ctype.h>
223a67b045Sdrh 
233a67b045Sdrh 
243a67b045Sdrh /*
253a67b045Sdrh ** Show a usage message on stderr then quit.
263a67b045Sdrh */
usage(const char * argv0)273a67b045Sdrh static void usage(const char *argv0){
283a67b045Sdrh   fprintf(stderr, "Usage: %s FILENAME COMMAND ...\n", argv0);
293a67b045Sdrh   fprintf(stderr,
303a67b045Sdrh     "COMMANDs:\n"
313a67b045Sdrh     "   apply DB           Apply the changeset to database file DB\n"
323a67b045Sdrh     "   concat FILE2 OUT   Concatenate FILENAME and FILE2 into OUT\n"
333a67b045Sdrh     "   dump               Show the complete content of the changeset\n"
343a67b045Sdrh     "   invert OUT         Write an inverted changeset into file OUT\n"
353a67b045Sdrh     "   sql                Give a pseudo-SQL rendering of the changeset\n"
363a67b045Sdrh   );
373a67b045Sdrh   exit(1);
383a67b045Sdrh }
393a67b045Sdrh 
403a67b045Sdrh /*
413a67b045Sdrh ** Read the content of a disk file into an in-memory buffer
423a67b045Sdrh */
readFile(const char * zFilename,int * pSz,void ** ppBuf)433a67b045Sdrh static void readFile(const char *zFilename, int *pSz, void **ppBuf){
443a67b045Sdrh   FILE *f;
452d77d80aSdrh   sqlite3_int64 sz;
463a67b045Sdrh   void *pBuf;
473a67b045Sdrh   f = fopen(zFilename, "rb");
483a67b045Sdrh   if( f==0 ){
493a67b045Sdrh     fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename);
503a67b045Sdrh     exit(1);
513a67b045Sdrh   }
523a67b045Sdrh   fseek(f, 0, SEEK_END);
532d77d80aSdrh   sz = ftell(f);
543a67b045Sdrh   rewind(f);
552d77d80aSdrh   pBuf = sqlite3_malloc64( sz ? sz : 1 );
563a67b045Sdrh   if( pBuf==0 ){
573a67b045Sdrh     fprintf(stderr, "cannot allocate %d to hold content of \"%s\"\n",
58*065f3bf4Smistachkin             (int)sz, zFilename);
593a67b045Sdrh     exit(1);
603a67b045Sdrh   }
613a67b045Sdrh   if( sz>0 ){
62*065f3bf4Smistachkin     if( fread(pBuf, (size_t)sz, 1, f)!=1 ){
63*065f3bf4Smistachkin       fprintf(stderr, "cannot read all %d bytes of \"%s\"\n",
64*065f3bf4Smistachkin               (int)sz, zFilename);
653a67b045Sdrh       exit(1);
663a67b045Sdrh     }
673a67b045Sdrh     fclose(f);
683a67b045Sdrh   }
69*065f3bf4Smistachkin   *pSz = (int)sz;
703a67b045Sdrh   *ppBuf = pBuf;
713a67b045Sdrh }
723a67b045Sdrh 
733a67b045Sdrh /* Array for converting from half-bytes (nybbles) into ASCII hex
743a67b045Sdrh ** digits. */
753a67b045Sdrh static const char hexdigits[] = {
763a67b045Sdrh   '0', '1', '2', '3', '4', '5', '6', '7',
773a67b045Sdrh   '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
783a67b045Sdrh };
793a67b045Sdrh 
803a67b045Sdrh /*
813a67b045Sdrh ** Render an sqlite3_value as an SQL string.
823a67b045Sdrh */
renderValue(sqlite3_value * pVal)833a67b045Sdrh static void renderValue(sqlite3_value *pVal){
843a67b045Sdrh   switch( sqlite3_value_type(pVal) ){
853a67b045Sdrh     case SQLITE_FLOAT: {
863a67b045Sdrh       double r1;
873a67b045Sdrh       char zBuf[50];
883a67b045Sdrh       r1 = sqlite3_value_double(pVal);
893a67b045Sdrh       sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
903a67b045Sdrh       printf("%s", zBuf);
913a67b045Sdrh       break;
923a67b045Sdrh     }
933a67b045Sdrh     case SQLITE_INTEGER: {
943a67b045Sdrh       printf("%lld", sqlite3_value_int64(pVal));
953a67b045Sdrh       break;
963a67b045Sdrh     }
973a67b045Sdrh     case SQLITE_BLOB: {
983a67b045Sdrh       char const *zBlob = sqlite3_value_blob(pVal);
993a67b045Sdrh       int nBlob = sqlite3_value_bytes(pVal);
1003a67b045Sdrh       int i;
1013a67b045Sdrh       printf("x'");
1023a67b045Sdrh       for(i=0; i<nBlob; i++){
1033a67b045Sdrh         putchar(hexdigits[(zBlob[i]>>4)&0x0F]);
1043a67b045Sdrh         putchar(hexdigits[(zBlob[i])&0x0F]);
1053a67b045Sdrh       }
1063a67b045Sdrh       putchar('\'');
1073a67b045Sdrh       break;
1083a67b045Sdrh     }
1093a67b045Sdrh     case SQLITE_TEXT: {
1103a67b045Sdrh       const unsigned char *zArg = sqlite3_value_text(pVal);
1113a67b045Sdrh       putchar('\'');
1123a67b045Sdrh       while( zArg[0] ){
1133a67b045Sdrh         putchar(zArg[0]);
1143a67b045Sdrh         if( zArg[0]=='\'' ) putchar(zArg[0]);
1153a67b045Sdrh         zArg++;
1163a67b045Sdrh       }
1173a67b045Sdrh       putchar('\'');
1183a67b045Sdrh       break;
1193a67b045Sdrh     }
1203a67b045Sdrh     default: {
1213a67b045Sdrh       assert( sqlite3_value_type(pVal)==SQLITE_NULL );
1223a67b045Sdrh       printf("NULL");
1233a67b045Sdrh       break;
1243a67b045Sdrh     }
1253a67b045Sdrh   }
1263a67b045Sdrh }
1273a67b045Sdrh 
12803168cacSdrh /*
12903168cacSdrh ** Number of conflicts seen
13003168cacSdrh */
13103168cacSdrh static int nConflict = 0;
13203168cacSdrh 
13303168cacSdrh /*
13403168cacSdrh ** The conflict callback
13503168cacSdrh */
conflictCallback(void * pCtx,int eConflict,sqlite3_changeset_iter * pIter)13603168cacSdrh static int conflictCallback(
13703168cacSdrh   void *pCtx,
13803168cacSdrh   int eConflict,
13903168cacSdrh   sqlite3_changeset_iter *pIter
14003168cacSdrh ){
14103168cacSdrh   int op, bIndirect, nCol, i;
14203168cacSdrh   const char *zTab;
14303168cacSdrh   unsigned char *abPK;
14403168cacSdrh   const char *zType = "";
14503168cacSdrh   const char *zOp = "";
14603168cacSdrh   const char *zSep = " ";
14703168cacSdrh 
14803168cacSdrh   nConflict++;
14903168cacSdrh   sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
15003168cacSdrh   sqlite3changeset_pk(pIter, &abPK, 0);
15103168cacSdrh   switch( eConflict ){
15203168cacSdrh     case SQLITE_CHANGESET_DATA:         zType = "DATA";         break;
15303168cacSdrh     case SQLITE_CHANGESET_NOTFOUND:     zType = "NOTFOUND";     break;
15403168cacSdrh     case SQLITE_CHANGESET_CONFLICT:     zType = "PRIMARY KEY";  break;
15503168cacSdrh     case SQLITE_CHANGESET_FOREIGN_KEY:  zType = "FOREIGN KEY";  break;
15603168cacSdrh     case SQLITE_CHANGESET_CONSTRAINT:   zType = "CONSTRAINT";   break;
15703168cacSdrh   }
15803168cacSdrh   switch( op ){
15903168cacSdrh     case SQLITE_UPDATE:     zOp = "UPDATE of";     break;
16003168cacSdrh     case SQLITE_INSERT:     zOp = "INSERT into";   break;
16103168cacSdrh     case SQLITE_DELETE:     zOp = "DELETE from";   break;
16203168cacSdrh   }
16303168cacSdrh   printf("%s conflict on %s table %s with primary key", zType, zOp, zTab);
16403168cacSdrh   for(i=0; i<nCol; i++){
16503168cacSdrh     sqlite3_value *pVal;
16603168cacSdrh     if( abPK[i]==0 ) continue;
16703168cacSdrh     printf("%s", zSep);
16803168cacSdrh     if( op==SQLITE_INSERT ){
16903168cacSdrh       sqlite3changeset_new(pIter, i, &pVal);
17003168cacSdrh     }else{
17103168cacSdrh       sqlite3changeset_old(pIter, i, &pVal);
17203168cacSdrh     }
17303168cacSdrh     renderValue(pVal);
17403168cacSdrh     zSep = ",";
17503168cacSdrh   }
17603168cacSdrh   printf("\n");
17703168cacSdrh   return SQLITE_CHANGESET_OMIT;
17803168cacSdrh }
17903168cacSdrh 
main(int argc,char ** argv)1803a67b045Sdrh int main(int argc, char **argv){
1813a67b045Sdrh   int sz, rc;
1823a67b045Sdrh   void *pBuf = 0;
1833a67b045Sdrh   if( argc<3 ) usage(argv[0]);
1843a67b045Sdrh   readFile(argv[1], &sz, &pBuf);
1853a67b045Sdrh 
1863a67b045Sdrh   /* changeset FILENAME apply DB
1873a67b045Sdrh   ** Apply the changeset in FILENAME to the database file DB
1883a67b045Sdrh   */
1893a67b045Sdrh   if( strcmp(argv[2],"apply")==0 ){
19003168cacSdrh     sqlite3 *db;
19103168cacSdrh     if( argc!=4 ) usage(argv[0]);
19203168cacSdrh     rc = sqlite3_open(argv[3], &db);
19303168cacSdrh     if( rc!=SQLITE_OK ){
19403168cacSdrh       fprintf(stderr, "unable to open database file \"%s\": %s\n",
19503168cacSdrh               argv[3], sqlite3_errmsg(db));
19603168cacSdrh       sqlite3_close(db);
19703168cacSdrh       exit(1);
19803168cacSdrh     }
19903168cacSdrh     sqlite3_exec(db, "BEGIN", 0, 0, 0);
20003168cacSdrh     nConflict = 0;
20103168cacSdrh     rc = sqlite3changeset_apply(db, sz, pBuf, 0, conflictCallback, 0);
20203168cacSdrh     if( rc ){
20303168cacSdrh       fprintf(stderr, "sqlite3changeset_apply() returned %d\n", rc);
20403168cacSdrh     }
20503168cacSdrh     if( nConflict ){
20603168cacSdrh       fprintf(stderr, "%d conflicts - no changes applied\n", nConflict);
20703168cacSdrh       sqlite3_exec(db, "ROLLBACK", 0, 0, 0);
20803168cacSdrh     }else if( rc ){
20903168cacSdrh       fprintf(stderr, "sqlite3changeset_apply() returns %d "
21003168cacSdrh                       "- no changes applied\n", rc);
21103168cacSdrh       sqlite3_exec(db, "ROLLBACK", 0, 0, 0);
21203168cacSdrh     }else{
21303168cacSdrh       sqlite3_exec(db, "COMMIT", 0, 0, 0);
21403168cacSdrh     }
21503168cacSdrh     sqlite3_close(db);
2163a67b045Sdrh   }else
2173a67b045Sdrh 
2183a67b045Sdrh   /* changeset FILENAME concat FILE2 OUT
2193a67b045Sdrh   ** Add changeset FILE2 onto the end of the changeset in FILENAME
2203a67b045Sdrh   ** and write the result into OUT.
2213a67b045Sdrh   */
2223a67b045Sdrh   if( strcmp(argv[2],"concat")==0 ){
22303168cacSdrh     int szB;
22403168cacSdrh     void *pB;
22503168cacSdrh     int szOut;
22603168cacSdrh     void *pOutBuf;
22703168cacSdrh     FILE *out;
22803168cacSdrh     const char *zOut = argv[4];
22903168cacSdrh     if( argc!=5 ) usage(argv[0]);
23003168cacSdrh     out = fopen(zOut, "wb");
23103168cacSdrh     if( out==0 ){
23203168cacSdrh       fprintf(stderr, "cannot open \"%s\" for writing\n", zOut);
23303168cacSdrh       exit(1);
23403168cacSdrh     }
23503168cacSdrh     readFile(argv[3], &szB, &pB);
2362967e0ccSdrh     rc = sqlite3changeset_concat(sz, pBuf, szB, pB, &szOut, &pOutBuf);
2372967e0ccSdrh     if( rc!=SQLITE_OK ){
2382967e0ccSdrh       fprintf(stderr, "sqlite3changeset_concat() returns %d\n", rc);
2392967e0ccSdrh     }else if( szOut>0 && fwrite(pOutBuf, szOut, 1, out)!=1 ){
24003168cacSdrh       fprintf(stderr, "unable to write all %d bytes of output to \"%s\"\n",
24103168cacSdrh               szOut, zOut);
24203168cacSdrh     }
24303168cacSdrh     fclose(out);
24403168cacSdrh     sqlite3_free(pOutBuf);
24503168cacSdrh     sqlite3_free(pB);
2463a67b045Sdrh   }else
2473a67b045Sdrh 
2483a67b045Sdrh   /* changeset FILENAME dump
2493a67b045Sdrh   ** Show the complete content of the changeset in FILENAME
2503a67b045Sdrh   */
2513a67b045Sdrh   if( strcmp(argv[2],"dump")==0 ){
2523a67b045Sdrh     int cnt = 0;
2533a67b045Sdrh     int i;
2543a67b045Sdrh     sqlite3_changeset_iter *pIter;
2553a67b045Sdrh     rc = sqlite3changeset_start(&pIter, sz, pBuf);
2563a67b045Sdrh     if( rc!=SQLITE_OK ){
2573a67b045Sdrh       fprintf(stderr, "sqlite3changeset_start() returns %d\n", rc);
2583a67b045Sdrh       exit(1);
2593a67b045Sdrh     }
2603a67b045Sdrh     while( sqlite3changeset_next(pIter)==SQLITE_ROW ){
2613a67b045Sdrh       int op, bIndirect, nCol;
2623a67b045Sdrh       const char *zTab;
2633a67b045Sdrh       unsigned char *abPK;
2643a67b045Sdrh       sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
2653a67b045Sdrh       cnt++;
2663a67b045Sdrh       printf("%d: %s table=[%s] indirect=%d nColumn=%d\n",
2673a67b045Sdrh              cnt, op==SQLITE_INSERT ? "INSERT" :
2683a67b045Sdrh                        op==SQLITE_UPDATE ? "UPDATE" : "DELETE",
2693a67b045Sdrh              zTab, bIndirect, nCol);
2703a67b045Sdrh       sqlite3changeset_pk(pIter, &abPK, 0);
2713a67b045Sdrh       for(i=0; i<nCol; i++){
2723a67b045Sdrh         sqlite3_value *pVal;
2733a67b045Sdrh         pVal = 0;
2743a67b045Sdrh         sqlite3changeset_old(pIter, i, &pVal);
2753a67b045Sdrh         if( pVal ){
2763a67b045Sdrh           printf("    old[%d]%s = ", i, abPK[i] ? "pk" : "  ");
2773a67b045Sdrh           renderValue(pVal);
2783a67b045Sdrh           printf("\n");
2793a67b045Sdrh         }
2803a67b045Sdrh         pVal = 0;
2813a67b045Sdrh         sqlite3changeset_new(pIter, i, &pVal);
2823a67b045Sdrh         if( pVal ){
2833a67b045Sdrh           printf("    new[%d]%s = ", i, abPK[i] ? "pk" : "  ");
2843a67b045Sdrh           renderValue(pVal);
2853a67b045Sdrh           printf("\n");
2863a67b045Sdrh         }
2873a67b045Sdrh       }
2883a67b045Sdrh     }
2893a67b045Sdrh     sqlite3changeset_finalize(pIter);
2903a67b045Sdrh   }else
2913a67b045Sdrh 
2923a67b045Sdrh   /* changeset FILENAME invert OUT
2933a67b045Sdrh   ** Invert the changes in FILENAME and writes the result on OUT
2943a67b045Sdrh   */
2953a67b045Sdrh   if( strcmp(argv[2],"invert")==0 ){
2963a67b045Sdrh     FILE *out;
2973a67b045Sdrh     int szOut = 0;
2983a67b045Sdrh     void *pOutBuf = 0;
29903168cacSdrh     const char *zOut = argv[3];
3003a67b045Sdrh     if( argc!=4 ) usage(argv[0]);
30103168cacSdrh     out = fopen(zOut, "wb");
3023a67b045Sdrh     if( out==0 ){
30303168cacSdrh       fprintf(stderr, "cannot open \"%s\" for writing\n", zOut);
3043a67b045Sdrh       exit(1);
3053a67b045Sdrh     }
3062967e0ccSdrh     rc = sqlite3changeset_invert(sz, pBuf, &szOut, &pOutBuf);
3072967e0ccSdrh     if( rc!=SQLITE_OK ){
3082967e0ccSdrh       fprintf(stderr, "sqlite3changeset_invert() returns %d\n", rc);
3092967e0ccSdrh     }else if( szOut>0 && fwrite(pOutBuf, szOut, 1, out)!=1 ){
3103a67b045Sdrh       fprintf(stderr, "unable to write all %d bytes of output to \"%s\"\n",
31103168cacSdrh               szOut, zOut);
3123a67b045Sdrh     }
3133a67b045Sdrh     fclose(out);
3143a67b045Sdrh     sqlite3_free(pOutBuf);
3153a67b045Sdrh   }else
3163a67b045Sdrh 
3173a67b045Sdrh   /* changeset FILE sql
3183a67b045Sdrh   ** Show the content of the changeset as pseudo-SQL
3193a67b045Sdrh   */
3203a67b045Sdrh   if( strcmp(argv[2],"sql")==0 ){
3213a67b045Sdrh     int cnt = 0;
3223a67b045Sdrh     char *zPrevTab = 0;
3233a67b045Sdrh     char *zSQLTabName = 0;
3243a67b045Sdrh     sqlite3_changeset_iter *pIter = 0;
3253a67b045Sdrh     rc = sqlite3changeset_start(&pIter, sz, pBuf);
3263a67b045Sdrh     if( rc!=SQLITE_OK ){
3273a67b045Sdrh       fprintf(stderr, "sqlite3changeset_start() returns %d\n", rc);
3283a67b045Sdrh       exit(1);
3293a67b045Sdrh     }
3303a67b045Sdrh     printf("BEGIN;\n");
3313a67b045Sdrh     while( sqlite3changeset_next(pIter)==SQLITE_ROW ){
3323a67b045Sdrh       int op, bIndirect, nCol;
3333a67b045Sdrh       const char *zTab;
3343a67b045Sdrh       sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
3353a67b045Sdrh       cnt++;
3363a67b045Sdrh       if( zPrevTab==0 || strcmp(zPrevTab,zTab)!=0 ){
3373a67b045Sdrh         sqlite3_free(zPrevTab);
3383a67b045Sdrh         sqlite3_free(zSQLTabName);
3393a67b045Sdrh         zPrevTab = sqlite3_mprintf("%s", zTab);
3403a67b045Sdrh         if( !isalnum(zTab[0]) || sqlite3_strglob("*[^a-zA-Z0-9]*",zTab)==0 ){
3413a67b045Sdrh           zSQLTabName = sqlite3_mprintf("\"%w\"", zTab);
3423a67b045Sdrh         }else{
3433a67b045Sdrh           zSQLTabName = sqlite3_mprintf("%s", zTab);
3443a67b045Sdrh         }
3453a67b045Sdrh         printf("/****** Changes for table %s ***************/\n", zSQLTabName);
3463a67b045Sdrh       }
3473a67b045Sdrh       switch( op ){
3483a67b045Sdrh         case SQLITE_DELETE: {
3493a67b045Sdrh           unsigned char *abPK;
3503a67b045Sdrh           int i;
3513a67b045Sdrh           const char *zSep = " ";
3523a67b045Sdrh           sqlite3changeset_pk(pIter, &abPK, 0);
3533a67b045Sdrh           printf("/* %d */ DELETE FROM %s WHERE", cnt, zSQLTabName);
3543a67b045Sdrh           for(i=0; i<nCol; i++){
3553a67b045Sdrh             sqlite3_value *pVal;
3563a67b045Sdrh             if( abPK[i]==0 ) continue;
3573a67b045Sdrh             printf("%sc%d=", zSep, i+1);
3583a67b045Sdrh             zSep = " AND ";
3593a67b045Sdrh             sqlite3changeset_old(pIter, i, &pVal);
3603a67b045Sdrh             renderValue(pVal);
3613a67b045Sdrh           }
3623a67b045Sdrh           printf(";\n");
3633a67b045Sdrh           break;
3643a67b045Sdrh         }
3653a67b045Sdrh         case SQLITE_UPDATE: {
3663a67b045Sdrh           unsigned char *abPK;
3673a67b045Sdrh           int i;
3683a67b045Sdrh           const char *zSep = " ";
3693a67b045Sdrh           sqlite3changeset_pk(pIter, &abPK, 0);
3703a67b045Sdrh           printf("/* %d */ UPDATE %s SET", cnt, zSQLTabName);
3713a67b045Sdrh           for(i=0; i<nCol; i++){
3723a67b045Sdrh             sqlite3_value *pVal = 0;
3733a67b045Sdrh             sqlite3changeset_new(pIter, i, &pVal);
3743a67b045Sdrh             if( pVal ){
3753a67b045Sdrh               printf("%sc%d=", zSep, i+1);
3763a67b045Sdrh               zSep = ", ";
3773a67b045Sdrh               renderValue(pVal);
3783a67b045Sdrh             }
3793a67b045Sdrh           }
3803a67b045Sdrh           printf(" WHERE");
3813a67b045Sdrh           zSep = " ";
3823a67b045Sdrh           for(i=0; i<nCol; i++){
3833a67b045Sdrh             sqlite3_value *pVal;
3843a67b045Sdrh             if( abPK[i]==0 ) continue;
3853a67b045Sdrh             printf("%sc%d=", zSep, i+1);
3863a67b045Sdrh             zSep = " AND ";
3873a67b045Sdrh             sqlite3changeset_old(pIter, i, &pVal);
3883a67b045Sdrh             renderValue(pVal);
3893a67b045Sdrh           }
3903a67b045Sdrh           printf(";\n");
3913a67b045Sdrh           break;
3923a67b045Sdrh         }
3933a67b045Sdrh         case SQLITE_INSERT: {
3943a67b045Sdrh           int i;
3953a67b045Sdrh           printf("/* %d */ INSERT INTO %s VALUES", cnt, zSQLTabName);
3963a67b045Sdrh           for(i=0; i<nCol; i++){
3973a67b045Sdrh             sqlite3_value *pVal;
3983a67b045Sdrh             printf("%c", i==0 ? '(' : ',');
3993a67b045Sdrh             sqlite3changeset_new(pIter, i, &pVal);
4003a67b045Sdrh             renderValue(pVal);
4013a67b045Sdrh           }
4023a67b045Sdrh           printf(");\n");
4033a67b045Sdrh           break;
4043a67b045Sdrh         }
4053a67b045Sdrh       }
4063a67b045Sdrh     }
4073a67b045Sdrh     printf("COMMIT;\n");
4083a67b045Sdrh     sqlite3changeset_finalize(pIter);
4093a67b045Sdrh     sqlite3_free(zPrevTab);
4103a67b045Sdrh     sqlite3_free(zSQLTabName);
4113a67b045Sdrh   }else
4123a67b045Sdrh 
4133a67b045Sdrh   /* If nothing else matches, show the usage comment */
4143a67b045Sdrh   usage(argv[0]);
4153a67b045Sdrh   sqlite3_free(pBuf);
4163a67b045Sdrh   return 0;
4173a67b045Sdrh }
418