summaryrefslogtreecommitdiffstats
path: root/ext/session/changeset.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/session/changeset.c')
-rw-r--r--ext/session/changeset.c417
1 files changed, 417 insertions, 0 deletions
diff --git a/ext/session/changeset.c b/ext/session/changeset.c
new file mode 100644
index 0000000..9cf6294
--- /dev/null
+++ b/ext/session/changeset.c
@@ -0,0 +1,417 @@
+/*
+** 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;
+ 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<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;
+}