Index: third_party/sqlite/src/ext/session/changeset.c |
diff --git a/third_party/sqlite/src/ext/session/changeset.c b/third_party/sqlite/src/ext/session/changeset.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..edb43ff8e52d601a70327bedea746b02ad7627ad |
--- /dev/null |
+++ b/third_party/sqlite/src/ext/session/changeset.c |
@@ -0,0 +1,416 @@ |
+/* |
+** 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; |
+} |