]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the "changeset" command-line utility for getting an ASCII dump of
authordrh <drh@noemail.net>
Mon, 18 Aug 2014 17:56:31 +0000 (17:56 +0000)
committerdrh <drh@noemail.net>
Mon, 18 Aug 2014 17:56:31 +0000 (17:56 +0000)
change sets.

FossilOrigin-Name: 55bb3544a6b474c04853270067a35ca4b0079f52

ext/session/changeset.c [new file with mode: 0644]
main.mk
manifest
manifest.uuid
src/shell.c

diff --git a/ext/session/changeset.c b/ext/session/changeset.c
new file mode 100644 (file)
index 0000000..62817a6
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+** 2014-08-18
+**
+** 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 contains code to implement the "changeset" command line
+** utility for displaying and transforming changesets generated by
+** the Sessions extension.
+*/
+#include "sqlite3.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+
+/*
+** Show a usage message on stderr then quit.
+*/
+static void usage(const char *argv0){
+  fprintf(stderr, "Usage: %s FILENAME COMMAND ...\n", argv0);
+  fprintf(stderr,
+    "COMMANDs:\n"
+    "   apply DB           Apply the changeset to database file DB\n"
+    "   concat FILE2 OUT   Concatenate FILENAME and FILE2 into OUT\n"
+    "   dump               Show the complete content of the changeset\n"
+    "   invert OUT         Write an inverted changeset into file OUT\n"
+    "   sql                Give a pseudo-SQL rendering of the changeset\n"
+  );
+  exit(1);
+}
+
+/*
+** Read the content of a disk file into an in-memory buffer
+*/
+static void readFile(const char *zFilename, int *pSz, void **ppBuf){
+  FILE *f;
+  int sz;
+  void *pBuf;
+  f = fopen(zFilename, "rb");
+  if( f==0 ){
+    fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename);
+    exit(1);
+  }
+  fseek(f, 0, SEEK_END);
+  sz = (int)ftell(f);
+  rewind(f);
+  pBuf = sqlite3_malloc( sz ? sz : 1 );
+  if( pBuf==0 ){
+    fprintf(stderr, "cannot allocate %d to hold content of \"%s\"\n",
+            sz, zFilename);
+    exit(1);
+  }
+  if( sz>0 ){
+    if( fread(pBuf, sz, 1, f)!=1 ){
+      fprintf(stderr, "cannot read all %d bytes of \"%s\"\n", sz, zFilename);
+      exit(1);
+    }
+    fclose(f);
+  }
+  *pSz = sz;
+  *ppBuf = pBuf;
+}
+
+/* Array for converting from half-bytes (nybbles) into ASCII hex
+** digits. */
+static const char hexdigits[] = {
+  '0', '1', '2', '3', '4', '5', '6', '7',
+  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 
+};
+
+/*
+** Render an sqlite3_value as an SQL string.
+*/
+static void renderValue(sqlite3_value *pVal){
+  switch( sqlite3_value_type(pVal) ){
+    case SQLITE_FLOAT: {
+      double r1;
+      char zBuf[50];
+      r1 = sqlite3_value_double(pVal);
+      sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
+      printf("%s", zBuf);
+      break;
+    }
+    case SQLITE_INTEGER: {
+      printf("%lld", sqlite3_value_int64(pVal));
+      break;
+    }
+    case SQLITE_BLOB: {
+      char const *zBlob = sqlite3_value_blob(pVal);
+      int nBlob = sqlite3_value_bytes(pVal);
+      int i;
+      printf("x'");
+      for(i=0; i<nBlob; i++){
+        putchar(hexdigits[(zBlob[i]>>4)&0x0F]);
+        putchar(hexdigits[(zBlob[i])&0x0F]);
+      }
+      putchar('\'');
+      break;
+    }
+    case SQLITE_TEXT: {
+      const unsigned char *zArg = sqlite3_value_text(pVal);
+      putchar('\'');
+      while( zArg[0] ){
+        putchar(zArg[0]);
+        if( zArg[0]=='\'' ) putchar(zArg[0]);
+        zArg++;
+      }
+      putchar('\'');
+      break;
+    }
+    default: {
+      assert( sqlite3_value_type(pVal)==SQLITE_NULL );
+      printf("NULL");
+      break;
+    }
+  }
+}
+
+int main(int argc, char **argv){
+  int sz, rc;
+  void *pBuf = 0;
+  if( argc<3 ) usage(argv[0]);
+  readFile(argv[1], &sz, &pBuf);
+
+  /* changeset FILENAME apply DB
+  ** Apply the changeset in FILENAME to the database file DB
+  */
+  if( strcmp(argv[2],"apply")==0 ){
+    fprintf(stderr, "not yet implemented\n");
+  }else
+
+  /* changeset FILENAME concat FILE2 OUT
+  ** Add changeset FILE2 onto the end of the changeset in FILENAME
+  ** and write the result into OUT.
+  */
+  if( strcmp(argv[2],"concat")==0 ){
+    fprintf(stderr, "not yet implemented\n");
+  }else
+
+  /* changeset FILENAME dump
+  ** Show the complete content of the changeset in FILENAME
+  */
+  if( strcmp(argv[2],"dump")==0 ){
+    int cnt = 0;
+    int i;
+    sqlite3_changeset_iter *pIter;
+    rc = sqlite3changeset_start(&pIter, sz, pBuf);
+    if( rc!=SQLITE_OK ){
+      fprintf(stderr, "sqlite3changeset_start() returns %d\n", rc);
+      exit(1);
+    }
+    while( sqlite3changeset_next(pIter)==SQLITE_ROW ){
+      int op, bIndirect, nCol;
+      const char *zTab;
+      unsigned char *abPK;
+      sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
+      cnt++;
+      printf("%d: %s table=[%s] indirect=%d nColumn=%d\n",
+             cnt, op==SQLITE_INSERT ? "INSERT" :
+                       op==SQLITE_UPDATE ? "UPDATE" : "DELETE",
+             zTab, bIndirect, nCol);
+      sqlite3changeset_pk(pIter, &abPK, 0);
+      for(i=0; i<nCol; i++){
+        sqlite3_value *pVal;
+        pVal = 0;
+        sqlite3changeset_old(pIter, i, &pVal);
+        if( pVal ){
+          printf("    old[%d]%s = ", i, abPK[i] ? "pk" : "  ");
+          renderValue(pVal);
+          printf("\n");
+        }
+        pVal = 0;
+        sqlite3changeset_new(pIter, i, &pVal);
+        if( pVal ){
+          printf("    new[%d]%s = ", i, abPK[i] ? "pk" : "  ");
+          renderValue(pVal);
+          printf("\n");
+        }
+      }
+    }
+    sqlite3changeset_finalize(pIter);
+  }else
+
+  /* changeset FILENAME invert OUT
+  ** Invert the changes in FILENAME and writes the result on OUT
+  */
+  if( strcmp(argv[2],"invert")==0 ){
+    FILE *out;
+    int szOut = 0;
+    void *pOutBuf = 0;
+    if( argc!=4 ) usage(argv[0]);
+    out = fopen(argv[3], "wb");
+    if( out==0 ){
+      fprintf(stderr, "cannot open \"%s\" for writing\n", argv[3]);
+      exit(1);
+    }
+    sqlite3changeset_invert(sz, pBuf, &szOut, &pOutBuf);
+    if( fwrite(pOutBuf, szOut, 1, out)!=1 ){
+      fprintf(stderr, "unable to write all %d bytes of output to \"%s\"\n",
+              szOut, argv[3]);
+    }
+    fclose(out);
+    sqlite3_free(pOutBuf);
+  }else
+
+  /* changeset FILE sql
+  ** Show the content of the changeset as pseudo-SQL
+  */
+  if( strcmp(argv[2],"sql")==0 ){
+    int cnt = 0;
+    char *zPrevTab = 0;
+    char *zSQLTabName = 0;
+    sqlite3_changeset_iter *pIter = 0;
+    rc = sqlite3changeset_start(&pIter, sz, pBuf);
+    if( rc!=SQLITE_OK ){
+      fprintf(stderr, "sqlite3changeset_start() returns %d\n", rc);
+      exit(1);
+    }
+    printf("BEGIN;\n");
+    while( sqlite3changeset_next(pIter)==SQLITE_ROW ){
+      int op, bIndirect, nCol;
+      const char *zTab;
+      sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
+      cnt++;
+      if( zPrevTab==0 || strcmp(zPrevTab,zTab)!=0 ){
+        sqlite3_free(zPrevTab);
+        sqlite3_free(zSQLTabName);
+        zPrevTab = sqlite3_mprintf("%s", zTab);
+        if( !isalnum(zTab[0]) || sqlite3_strglob("*[^a-zA-Z0-9]*",zTab)==0 ){
+          zSQLTabName = sqlite3_mprintf("\"%w\"", zTab);
+        }else{
+          zSQLTabName = sqlite3_mprintf("%s", zTab);
+        }
+        printf("/****** Changes for table %s ***************/\n", zSQLTabName);
+      }
+      switch( op ){
+        case SQLITE_DELETE: {
+          unsigned char *abPK;
+          int i;
+          const char *zSep = " ";
+          sqlite3changeset_pk(pIter, &abPK, 0);
+          printf("/* %d */ DELETE FROM %s WHERE", cnt, zSQLTabName);
+          for(i=0; i<nCol; i++){
+            sqlite3_value *pVal;
+            if( abPK[i]==0 ) continue;
+            printf("%sc%d=", zSep, i+1);
+            zSep = " AND ";
+            sqlite3changeset_old(pIter, i, &pVal);
+            renderValue(pVal);
+          }
+          printf(";\n");
+          break;
+        }
+        case SQLITE_UPDATE: {
+          unsigned char *abPK;
+          int i;
+          const char *zSep = " ";
+          sqlite3changeset_pk(pIter, &abPK, 0);
+          printf("/* %d */ UPDATE %s SET", cnt, zSQLTabName);
+          for(i=0; i<nCol; i++){
+            sqlite3_value *pVal = 0;
+            sqlite3changeset_new(pIter, i, &pVal);
+            if( pVal ){
+              printf("%sc%d=", zSep, i+1);
+              zSep = ", ";
+              renderValue(pVal);
+            }
+          }
+          printf(" WHERE");
+          zSep = " ";
+          for(i=0; i<nCol; i++){
+            sqlite3_value *pVal;
+            if( abPK[i]==0 ) continue;
+            printf("%sc%d=", zSep, i+1);
+            zSep = " AND ";
+            sqlite3changeset_old(pIter, i, &pVal);
+            renderValue(pVal);
+          }
+          printf(";\n");
+          break;
+        }
+        case SQLITE_INSERT: {
+          int i;
+          printf("/* %d */ INSERT INTO %s VALUES", cnt, zSQLTabName);
+          for(i=0; i<nCol; i++){
+            sqlite3_value *pVal;
+            printf("%c", i==0 ? '(' : ',');
+            sqlite3changeset_new(pIter, i, &pVal);
+            renderValue(pVal);
+          }
+          printf(");\n");
+          break;
+        }
+      }
+    }
+    printf("COMMIT;\n");
+    sqlite3changeset_finalize(pIter);
+    sqlite3_free(zPrevTab);
+    sqlite3_free(zSQLTabName);
+  }else
+
+  /* If nothing else matches, show the usage comment */
+  usage(argv[0]);
+  sqlite3_free(pBuf);
+  return 0; 
+}
diff --git a/main.mk b/main.mk
index 24e599ca5b9a96053c78a850da9576250c1a0476..13798354cbe43b6ad6c112cb8769a9d6861277d8 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -660,6 +660,10 @@ showwal$(EXE):     $(TOP)/tool/showwal.c sqlite3.o
        $(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o showwal$(EXE) \
                $(TOP)/tool/showwal.c sqlite3.o $(THREADLIB)
 
+changeset$(EXE):       $(TOP)/ext/session/changeset.c sqlite3.o
+       $(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o changeset$(EXE) \
+               $(TOP)/ext/session/changeset.c sqlite3.o $(THREADLIB)
+
 fts3view$(EXE):        $(TOP)/ext/fts3/tool/fts3view.c sqlite3.o
        $(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o fts3view$(EXE) \
                $(TOP)/ext/fts3/tool/fts3view.c sqlite3.o $(THREADLIB)
index 3a3abec595b73980af5dcb1a53744ef1779090c8..797e18e745cde9a86880e5bfd49e56fcf4c9e277 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Begin\sadding\scommands\sto\sthe\scommand-line\sinterface\sfor\sinteracting\swith\nthe\ssessions\sextension.\s\sThis\sis\sthe\sfirst\scheck-in\sof\sa\swork-in-progress.
-D 2014-08-18T15:08:26.073
+C Add\sthe\s"changeset"\scommand-line\sutility\sfor\sgetting\san\sASCII\sdump\sof\nchange\ssets.
+D 2014-08-18T17:56:31.306
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 639859a6f81bd15921ccd56ddbd6dfd335278377
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -143,6 +143,7 @@ F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea
 F ext/rtree/sqlite3rtree.h 83349d519fe5f518b3ea025d18dd1fe51b1684bd
 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
 F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
+F ext/session/changeset.c d46c6467c223717d0e4783115f350c5962d53589
 F ext/session/session1.test 894e3bc9f497c4fa07a2aa3271e3911f3670c3d8
 F ext/session/session2.test 99ca0da7ddb617d42bafd83adccf99f18ae0384b
 F ext/session/session3.test a7a9ce59b8d1e49e2cc23d81421ac485be0eea01
@@ -161,7 +162,7 @@ F ext/session/test_session.c 920ccb6d6e1df263cd9099563328094c230b2925
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk eb6742eba7ebb8dc18401220e7ce21788d41aaef
+F main.mk 95de2e32fbe62b10e28bd7a131e2b003b923a8b3
 F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
 F mkopcodeh.awk c6b3fa301db6ef7ac916b14c60868aeaec1337b5
 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
@@ -238,7 +239,7 @@ F src/random.c d10c1f85b6709ca97278428fd5db5bbb9c74eece
 F src/resolve.c 0ea356d32a5e884add23d1b9b4e8736681dd5697
 F src/rowset.c a9c9aae3234b44a6d7c6f5a3cadf90dce1e627be
 F src/select.c ea48e891406ccdf748f3eb02893e056d134a0fea
-F src/shell.c 6378fc281ab6c2c5ee46cf13f2657e8a187c3ce7
+F src/shell.c 0845863fc7e813aac148d3303faf9f93f86ad8c3
 F src/sqlite.h.in 021a1f5c50e83060675d994a6014fd409e611d9e
 F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
 F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc
@@ -1202,10 +1203,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 419d286a2fc465f6e0f9662909d0cc52a18eefa4
-R 23a3bfb6cd8e3a34baca1f6d98151d23
-T *branch * sessions_from_cli
-T *sym-sessions_from_cli *
-T -sym-sessions *
+P c2fcf0b9f4bdc48dfc6530bda4f531b94a833207
+R ef02979994221e0299bd184663702a4d
 U drh
-Z dbfbe7f80e47f8918e41f67c20d63911
+Z 24c20f108d2116be16c069d984599c3d
index c0786ba14fdfc754b645a01b67edb13e43282a72..4a8f3031800a6915720419e5c06cbfc6bbb41c4d 100644 (file)
@@ -1 +1 @@
-c2fcf0b9f4bdc48dfc6530bda4f531b94a833207
\ No newline at end of file
+55bb3544a6b474c04853270067a35ca4b0079f52
\ No newline at end of file
index 66f073a9a88fde97d7eadffb8a74a6adbcb497f4..f9ade61e946a93aaee054f8470221d9333c57aeb 100644 (file)
@@ -3169,6 +3169,7 @@ static int do_meta_command(char *zLine, ShellState *p){
     int nCmd = nArg - 1;
     int i;
     if( nArg<=1 ) goto session_syntax_error;
+    open_db(p, 0);
     if( nArg>=3 ){
       for(iSes=0; iSes<p->nSession; iSes++){
         if( strcmp(p->aSession[iSes].zName, azArg[1])==0 ) break;
@@ -3183,6 +3184,53 @@ static int do_meta_command(char *zLine, ShellState *p){
       }
     }
 
+    /* .session attach TABLE
+    ** Invoke the sqlite3session_attach() interface to attach a particular
+    ** table so that it is never filtered.
+    */
+    if( strcmp(azCmd[0],"attach")==0 ){
+      if( nCmd!=2 ) goto session_syntax_error;
+      if( pSession->p==0 ){
+        session_not_open:
+        fprintf(stderr, "ERROR: No sessions are open\n");
+      }else{
+        rc = sqlite3session_attach(pSession->p, azCmd[1]);
+        if( rc ){
+          fprintf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc);
+          rc = 0;
+        }
+      }
+    }else
+
+    /* .session changeset FILE
+    ** .session patchset FILE
+    ** Write a changeset or patchset into a file.  The file is overwritten.
+    */
+    if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
+      FILE *out = 0;
+      if( nCmd!=2 ) goto session_syntax_error;
+      if( pSession->p==0 ) goto session_not_open;
+      out = fopen(azCmd[1], "wb");
+      if( out==0 ){
+        fprintf(stderr, "ERROR: cannot open \"%s\" for writing\n", azCmd[1]);
+      }else{
+        int szChng;
+        void *pChng;
+        if( azCmd[0][0]=='c' ){
+          sqlite3session_changeset(pSession->p, &szChng, &pChng);
+        }else{
+          sqlite3session_patchset(pSession->p, &szChng, &pChng);
+        }
+        if( pChng 
+          && fwrite(pChng, szChng, 1, out)!=1 ){
+          fprintf(stderr, "ERROR: Failed to write entire %d-byte output\n",
+                  szChng);
+        }
+        sqlite3_free(pChng);
+        fclose(out);
+      }
+    }else
+
     /* .session close
     ** Close the identified session
     */
@@ -3225,6 +3273,7 @@ static int do_meta_command(char *zLine, ShellState *p){
       rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
       if( rc ){
         fprintf(stderr, "Cannot open session: error code=%d\n", rc);
+        rc = 0;
         goto meta_command_exit;
       }
       p->nSession++;