mirror of
				https://github.com/sqlite/sqlite.git
				synced 2025-11-03 16:53:36 +03:00 
			
		
		
		
	in the "changeset" command-line program. FossilOrigin-Name: b69f7dd1f35846c3bb9f4f160d50c4f03796f887
		
			
				
	
	
		
			417 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			417 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
** 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;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** Number of conflicts seen
 | 
						|
*/
 | 
						|
static int nConflict = 0;
 | 
						|
 | 
						|
/*
 | 
						|
** The conflict callback
 | 
						|
*/
 | 
						|
static int conflictCallback(
 | 
						|
  void *pCtx,
 | 
						|
  int eConflict,
 | 
						|
  sqlite3_changeset_iter *pIter
 | 
						|
){
 | 
						|
  int op, bIndirect, nCol, i;
 | 
						|
  const char *zTab;
 | 
						|
  unsigned char *abPK;
 | 
						|
  const char *zType = "";
 | 
						|
  const char *zOp = "";
 | 
						|
  const char *zSep = " ";
 | 
						|
 | 
						|
  nConflict++;
 | 
						|
  sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
 | 
						|
  sqlite3changeset_pk(pIter, &abPK, 0);
 | 
						|
  switch( eConflict ){
 | 
						|
    case SQLITE_CHANGESET_DATA:         zType = "DATA";         break;
 | 
						|
    case SQLITE_CHANGESET_NOTFOUND:     zType = "NOTFOUND";     break;
 | 
						|
    case SQLITE_CHANGESET_CONFLICT:     zType = "PRIMARY KEY";  break;
 | 
						|
    case SQLITE_CHANGESET_FOREIGN_KEY:  zType = "FOREIGN KEY";  break;
 | 
						|
    case SQLITE_CHANGESET_CONSTRAINT:   zType = "CONSTRAINT";   break;
 | 
						|
  }
 | 
						|
  switch( op ){
 | 
						|
    case SQLITE_UPDATE:     zOp = "UPDATE of";     break;
 | 
						|
    case SQLITE_INSERT:     zOp = "INSERT into";   break;
 | 
						|
    case SQLITE_DELETE:     zOp = "DELETE from";   break;
 | 
						|
  }
 | 
						|
  printf("%s conflict on %s table %s with primary key", zType, zOp, zTab);
 | 
						|
  for(i=0; i<nCol; i++){
 | 
						|
    sqlite3_value *pVal;
 | 
						|
    if( abPK[i]==0 ) continue;
 | 
						|
    printf("%s", zSep);
 | 
						|
    if( op==SQLITE_INSERT ){
 | 
						|
      sqlite3changeset_new(pIter, i, &pVal);
 | 
						|
    }else{
 | 
						|
      sqlite3changeset_old(pIter, i, &pVal);
 | 
						|
    }
 | 
						|
    renderValue(pVal);
 | 
						|
    zSep = ",";
 | 
						|
  }
 | 
						|
  printf("\n");
 | 
						|
  return SQLITE_CHANGESET_OMIT;
 | 
						|
}
 | 
						|
 | 
						|
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 ){
 | 
						|
    sqlite3 *db;
 | 
						|
    if( argc!=4 ) usage(argv[0]);
 | 
						|
    rc = sqlite3_open(argv[3], &db);
 | 
						|
    if( rc!=SQLITE_OK ){
 | 
						|
      fprintf(stderr, "unable to open database file \"%s\": %s\n",
 | 
						|
              argv[3], sqlite3_errmsg(db));
 | 
						|
      sqlite3_close(db);
 | 
						|
      exit(1);
 | 
						|
    }
 | 
						|
    sqlite3_exec(db, "BEGIN", 0, 0, 0);
 | 
						|
    nConflict = 0;
 | 
						|
    rc = sqlite3changeset_apply(db, sz, pBuf, 0, conflictCallback, 0);
 | 
						|
    if( rc ){
 | 
						|
      fprintf(stderr, "sqlite3changeset_apply() returned %d\n", rc);
 | 
						|
    }
 | 
						|
    if( nConflict ){
 | 
						|
      fprintf(stderr, "%d conflicts - no changes applied\n", nConflict);
 | 
						|
      sqlite3_exec(db, "ROLLBACK", 0, 0, 0);
 | 
						|
    }else if( rc ){
 | 
						|
      fprintf(stderr, "sqlite3changeset_apply() returns %d "
 | 
						|
                      "- no changes applied\n", rc);
 | 
						|
      sqlite3_exec(db, "ROLLBACK", 0, 0, 0);
 | 
						|
    }else{
 | 
						|
      sqlite3_exec(db, "COMMIT", 0, 0, 0);
 | 
						|
    }
 | 
						|
    sqlite3_close(db);
 | 
						|
  }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 ){
 | 
						|
    int szB;
 | 
						|
    void *pB;
 | 
						|
    int szOut;
 | 
						|
    void *pOutBuf;
 | 
						|
    FILE *out;
 | 
						|
    const char *zOut = argv[4];
 | 
						|
    if( argc!=5 ) usage(argv[0]);
 | 
						|
    out = fopen(zOut, "wb");
 | 
						|
    if( out==0 ){
 | 
						|
      fprintf(stderr, "cannot open \"%s\" for writing\n", zOut);
 | 
						|
      exit(1);
 | 
						|
    }
 | 
						|
    readFile(argv[3], &szB, &pB);
 | 
						|
    rc = sqlite3changeset_concat(sz, pBuf, szB, pB, &szOut, &pOutBuf);
 | 
						|
    if( rc!=SQLITE_OK ){
 | 
						|
      fprintf(stderr, "sqlite3changeset_concat() returns %d\n", rc);
 | 
						|
    }else if( szOut>0 && fwrite(pOutBuf, szOut, 1, out)!=1 ){
 | 
						|
      fprintf(stderr, "unable to write all %d bytes of output to \"%s\"\n",
 | 
						|
              szOut, zOut);
 | 
						|
    }
 | 
						|
    fclose(out);
 | 
						|
    sqlite3_free(pOutBuf);
 | 
						|
    sqlite3_free(pB);
 | 
						|
  }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;
 | 
						|
    const char *zOut = argv[3];
 | 
						|
    if( argc!=4 ) usage(argv[0]);
 | 
						|
    out = fopen(zOut, "wb");
 | 
						|
    if( out==0 ){
 | 
						|
      fprintf(stderr, "cannot open \"%s\" for writing\n", zOut);
 | 
						|
      exit(1);
 | 
						|
    }
 | 
						|
    rc = sqlite3changeset_invert(sz, pBuf, &szOut, &pOutBuf);
 | 
						|
    if( rc!=SQLITE_OK ){
 | 
						|
      fprintf(stderr, "sqlite3changeset_invert() returns %d\n", rc);
 | 
						|
    }else if( szOut>0 && fwrite(pOutBuf, szOut, 1, out)!=1 ){
 | 
						|
      fprintf(stderr, "unable to write all %d bytes of output to \"%s\"\n",
 | 
						|
              szOut, zOut);
 | 
						|
    }
 | 
						|
    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; 
 | 
						|
}
 |