/* ** 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 #include #include #include #include /* ** 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; sqlite3_int64 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 = ftell(f); rewind(f); pBuf = sqlite3_malloc64( sz ? sz : 1 ); if( pBuf==0 ){ fprintf(stderr, "cannot allocate %d to hold content of \"%s\"\n", (int)sz, zFilename); exit(1); } if( sz>0 ){ if( fread(pBuf, (size_t)sz, 1, f)!=1 ){ fprintf(stderr, "cannot read all %d bytes of \"%s\"\n", (int)sz, zFilename); exit(1); } fclose(f); } *pSz = (int)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>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; i0 && 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; i0 && 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