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