/ Check-in [31addb62]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add the "changeset" command-line tool for analyzing and manipulating changesets in files on disk. Add the ".session" command to the command-line tool.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1:31addb627fdbaeb908e0611ad82f6db7537428ea
User & Date: drh 2014-08-18 20:08:25
Context
2014-08-18
20:14
Fix a harmless compiler warning in the sessionAppendDelete() function. check-in: 1324d08d user: drh tags: sessions
20:08
Add the "changeset" command-line tool for analyzing and manipulating changesets in files on disk. Add the ".session" command to the command-line tool. check-in: 31addb62 user: drh tags: sessions
20:01
A reasonably complete implementation of the "changeset" command-line tool and the ".sessions" command in the command-line shell. Closed-Leaf check-in: 7b12f1f9 user: drh tags: sessions_from_cli
16:03
Add miscellaneous test cases to improve coverage of sessions module. check-in: 0fac6cff user: dan tags: sessions
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Added ext/session/changeset.c.

























































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
/*
** 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);
    sqlite3changeset_concat(sz, pBuf, szB, pB, &szOut, &pOutBuf);
    if( 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);
    }
    sqlite3changeset_invert(sz, pBuf, &szOut, &pOutBuf);
    if( 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; 
}

Changes to main.mk.

68
69
70
71
72
73
74


75
76
77
78
79
80
81
...
559
560
561
562
563
564
565



566
567
568
569
570
571
572
...
650
651
652
653
654
655
656




657
658
659
660
661
662
663
         pager.o pcache.o pcache1.o pragma.o prepare.o printf.o \
         random.o resolve.o rowset.o rtree.o select.o status.o \
         table.o tokenize.o trigger.o \
         update.o util.o vacuum.o \
         vdbeapi.o vdbeaux.o vdbeblob.o vdbemem.o vdbesort.o \
	 vdbetrace.o wal.o walker.o where.o utf.o vtab.o





# All of the source code files.
#
SRC = \
  $(TOP)/src/alter.c \
  $(TOP)/src/analyze.c \
................................................................................

fts3_write.o:	$(TOP)/ext/fts3/fts3_write.c $(HDR) $(EXTHDR)
	$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_write.c

rtree.o:	$(TOP)/ext/rtree/rtree.c $(HDR) $(EXTHDR)
	$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/rtree/rtree.c





# Rules for building test programs and for running tests
#
tclsqlite3:	$(TOP)/src/tclsqlite.c libsqlite3.a
	$(TCCX) $(TCL_FLAGS) -DTCLSH=1 -o tclsqlite3 \
		$(TOP)/src/tclsqlite.c libsqlite3.a $(LIBTCL) $(THREADLIB)

................................................................................
showjournal$(EXE):	$(TOP)/tool/showjournal.c sqlite3.o
	$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o showjournal$(EXE) \
		$(TOP)/tool/showjournal.c sqlite3.o $(THREADLIB)

showwal$(EXE):	$(TOP)/tool/showwal.c sqlite3.o
	$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o showwal$(EXE) \
		$(TOP)/tool/showwal.c sqlite3.o $(THREADLIB)





fts3view$(EXE):	$(TOP)/ext/fts3/tool/fts3view.c sqlite3.o
	$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o fts3view$(EXE) \
		$(TOP)/ext/fts3/tool/fts3view.c sqlite3.o $(THREADLIB)

rollback-test$(EXE):	$(TOP)/tool/rollback-test.c sqlite3.o
	$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o rollback-test$(EXE) \







>
>







 







>
>
>







 







>
>
>
>







68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
...
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
...
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
         pager.o pcache.o pcache1.o pragma.o prepare.o printf.o \
         random.o resolve.o rowset.o rtree.o select.o status.o \
         table.o tokenize.o trigger.o \
         update.o util.o vacuum.o \
         vdbeapi.o vdbeaux.o vdbeblob.o vdbemem.o vdbesort.o \
	 vdbetrace.o wal.o walker.o where.o utf.o vtab.o

LIBOBJ += sqlite3session.o



# All of the source code files.
#
SRC = \
  $(TOP)/src/alter.c \
  $(TOP)/src/analyze.c \
................................................................................

fts3_write.o:	$(TOP)/ext/fts3/fts3_write.c $(HDR) $(EXTHDR)
	$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_write.c

rtree.o:	$(TOP)/ext/rtree/rtree.c $(HDR) $(EXTHDR)
	$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/rtree/rtree.c

sqlite3session.o:	$(TOP)/ext/session/sqlite3session.c $(HDR) $(EXTHDR)
	$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/session/sqlite3session.c


# Rules for building test programs and for running tests
#
tclsqlite3:	$(TOP)/src/tclsqlite.c libsqlite3.a
	$(TCCX) $(TCL_FLAGS) -DTCLSH=1 -o tclsqlite3 \
		$(TOP)/src/tclsqlite.c libsqlite3.a $(LIBTCL) $(THREADLIB)

................................................................................
showjournal$(EXE):	$(TOP)/tool/showjournal.c sqlite3.o
	$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o showjournal$(EXE) \
		$(TOP)/tool/showjournal.c sqlite3.o $(THREADLIB)

showwal$(EXE):	$(TOP)/tool/showwal.c sqlite3.o
	$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o showwal$(EXE) \
		$(TOP)/tool/showwal.c sqlite3.o $(THREADLIB)

changeset$(EXE):	$(TOP)/ext/session/changeset.c sqlite3.o
	$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o changeset$(EXE) \
		$(TOP)/ext/session/changeset.c sqlite3.o $(THREADLIB)

fts3view$(EXE):	$(TOP)/ext/fts3/tool/fts3view.c sqlite3.o
	$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o fts3view$(EXE) \
		$(TOP)/ext/fts3/tool/fts3view.c sqlite3.o $(THREADLIB)

rollback-test$(EXE):	$(TOP)/tool/rollback-test.c sqlite3.o
	$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o rollback-test$(EXE) \

Changes to src/shell.c.

428
429
430
431
432
433
434













435
436
437
438
439
440
441
...
475
476
477
478
479
480
481




482
483
484
485
486
487
488
....
1632
1633
1634
1635
1636
1637
1638



1639
1640
1641
1642
1643
1644
1645
....
1646
1647
1648
1649
1650
1651
1652
























1653
1654
1655
1656
1657
1658
1659
....
1709
1710
1711
1712
1713
1714
1715













































1716
1717
1718
1719
1720
1721
1722
....
2861
2862
2863
2864
2865
2866
2867

2868
2869
2870
2871
2872
2873
2874
....
3081
3082
3083
3084
3085
3086
3087





























































































































































































3088
3089
3090
3091
3092
3093
3094
....
4130
4131
4132
4133
4134
4135
4136

4137
4138
4139
4140
4141
    fflush(stdout);
    zResult = local_getline(zPrior, stdin);
#endif
  }
  return zResult;
}














/*
** Shell output mode information from before ".explain on", 
** saved so that it can be restored by ".explain off"
*/
typedef struct SavedModeInfo SavedModeInfo;
struct SavedModeInfo {
  int valid;          /* Is there legit data in here? */
................................................................................
  char *zFreeOnClose;         /* Filename to free when closing */
  const char *zVfs;           /* Name of VFS to use */
  sqlite3_stmt *pStmt;   /* Current statement if any. */
  FILE *pLog;            /* Write log output here */
  int *aiIndent;         /* Array of indents used in MODE_Explain */
  int nIndent;           /* Size of array aiIndent[] */
  int iIndent;           /* Index of current op in aiIndent[] */




};

/*
** These are the allowed modes.
*/
#define MODE_Line     0  /* One column per line.  Blank line between records */
#define MODE_Column   1  /* One record per line in neat columns */
................................................................................
  ".restore ?DB? FILE     Restore content of DB (default \"main\") from FILE\n"
  ".save FILE             Write in-memory database into FILE\n"
  ".schema ?TABLE?        Show the CREATE statements\n"
  "                         If TABLE specified, only show tables matching\n"
  "                         LIKE pattern TABLE.\n"
  ".separator STRING ?NL? Change separator used by output mode and .import\n"
  "                         NL is the end-of-line mark for CSV\n"



  ".shell CMD ARGS...     Run CMD ARGS... in a system shell\n"
  ".show                  Show the current values for various settings\n"
  ".stats on|off          Turn stats on or off\n"
  ".system CMD ARGS...    Run CMD ARGS... in a system shell\n"
  ".tables ?TABLE?        List names of tables\n"
  "                         If TABLE specified, only list tables matching\n"
  "                         LIKE pattern TABLE.\n"
................................................................................
  ".timeout MS            Try opening locked tables for MS milliseconds\n"
  ".timer on|off          Turn SQL timer on or off\n"
  ".trace FILE|off        Output each SQL statement as it is run\n"
  ".vfsname ?AUX?         Print the name of the VFS stack\n"
  ".width NUM1 NUM2 ...   Set column widths for \"column\" mode\n"
  "                         Negative values right-justify\n"
;

























/* Forward reference */
static int process_input(ShellState *p, FILE *in);
/*
** Implementation of the "readfile(X)" SQL function.  The entire content
** of the file named X is read and returned as a BLOB.  NULL is returned
** if the file does not exist or is unreadable.
................................................................................
    rc = 0;
  }else{
    rc = fwrite(z, 1, sqlite3_value_bytes(argv[1]), out);
  }
  fclose(out);
  sqlite3_result_int64(context, rc);
}














































/*
** Make sure the database is open.  If it is not, then open it.  If
** the database fails to open, print an error message and exit.
*/
static void open_db(ShellState *p, int keepAlive){
  if( p->db==0 ){
................................................................................
    char *zNewFilename = 0;
    p->db = 0;
    if( nArg>=2 ){
      p->zDbFilename = zNewFilename = sqlite3_mprintf("%s", azArg[1]);
    }
    open_db(p, 1);
    if( p->db!=0 ){

      sqlite3_close(savedDb);
      sqlite3_free(p->zFreeOnClose);
      p->zFreeOnClose = zNewFilename;
    }else{
      sqlite3_free(zNewFilename);
      p->db = savedDb;
      p->zDbFilename = zSavedFilename;
................................................................................
    }else if( rc != SQLITE_OK ){
      fprintf(stderr,"Error: querying schema information\n");
      rc = 1;
    }else{
      rc = 0;
    }
  }else






























































































































































































#ifdef SQLITE_DEBUG
  /* Undocumented commands for internal testing.  Subject to change
  ** without notice. */
  if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){
    if( strncmp(azArg[0]+9, "boolean", n-9)==0 ){
      int i, v;
................................................................................
      }
    }else{
      rc = process_input(&data, stdin);
    }
  }
  set_table_name(&data, 0);
  if( data.db ){

    sqlite3_close(data.db);
  }
  sqlite3_free(data.zFreeOnClose); 
  return rc;
}







>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>







 







>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>





428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
...
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
....
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
....
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
....
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
....
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
....
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
....
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
    fflush(stdout);
    zResult = local_getline(zPrior, stdin);
#endif
  }
  return zResult;
}

#if defined(SQLITE_ENABLE_SESSION)
/*
** State information for a single open session
*/
typedef struct OpenSession OpenSession;
struct OpenSession {
  char *zName;             /* Symbolic name for this session */
  int nFilter;             /* Number of xFilter rejection GLOB patterns */
  char **azFilter;         /* Array of xFilter rejection GLOB patterns */
  sqlite3_session *p;      /* The open session */
};
#endif

/*
** Shell output mode information from before ".explain on", 
** saved so that it can be restored by ".explain off"
*/
typedef struct SavedModeInfo SavedModeInfo;
struct SavedModeInfo {
  int valid;          /* Is there legit data in here? */
................................................................................
  char *zFreeOnClose;         /* Filename to free when closing */
  const char *zVfs;           /* Name of VFS to use */
  sqlite3_stmt *pStmt;   /* Current statement if any. */
  FILE *pLog;            /* Write log output here */
  int *aiIndent;         /* Array of indents used in MODE_Explain */
  int nIndent;           /* Size of array aiIndent[] */
  int iIndent;           /* Index of current op in aiIndent[] */
#if defined(SQLITE_ENABLE_SESSION)
  int nSession;             /* Number of active sessions */
  OpenSession aSession[4];  /* Array of sessions.  [0] is in focus. */
#endif
};

/*
** These are the allowed modes.
*/
#define MODE_Line     0  /* One column per line.  Blank line between records */
#define MODE_Column   1  /* One record per line in neat columns */
................................................................................
  ".restore ?DB? FILE     Restore content of DB (default \"main\") from FILE\n"
  ".save FILE             Write in-memory database into FILE\n"
  ".schema ?TABLE?        Show the CREATE statements\n"
  "                         If TABLE specified, only show tables matching\n"
  "                         LIKE pattern TABLE.\n"
  ".separator STRING ?NL? Change separator used by output mode and .import\n"
  "                         NL is the end-of-line mark for CSV\n"
#if defined(SQLITE_ENABLE_SESSION)
  ".session CMD ...       Create or control sessions\n"
#endif
  ".shell CMD ARGS...     Run CMD ARGS... in a system shell\n"
  ".show                  Show the current values for various settings\n"
  ".stats on|off          Turn stats on or off\n"
  ".system CMD ARGS...    Run CMD ARGS... in a system shell\n"
  ".tables ?TABLE?        List names of tables\n"
  "                         If TABLE specified, only list tables matching\n"
  "                         LIKE pattern TABLE.\n"
................................................................................
  ".timeout MS            Try opening locked tables for MS milliseconds\n"
  ".timer on|off          Turn SQL timer on or off\n"
  ".trace FILE|off        Output each SQL statement as it is run\n"
  ".vfsname ?AUX?         Print the name of the VFS stack\n"
  ".width NUM1 NUM2 ...   Set column widths for \"column\" mode\n"
  "                         Negative values right-justify\n"
;

#if defined(SQLITE_ENABLE_SESSION)
/*
** Print help information for the ".sessions" command
*/
void session_help(ShellState *p){
  fprintf(p->out,
    ".session ?NAME? SUBCOMMAND ?ARGS...?\n"
    "If ?NAME? is omitted, the first defined session is used.\n"
    "Subcommands:\n"
    "   attach TABLE             Attach TABLE\n"
    "   changeset FILE           Write a changeset into FILE\n"
    "   close                    Close one session\n"
    "   enable ?BOOLEAN?         Set or query the enable bit\n"
    "   filter GLOB...           Reject tables matching GLOBs\n" 
    "   indirect ?BOOLEAN?       Mark or query the indirect status\n"
    "   isempty                  Query whether the session is empty\n"
    "   list                     List currently open session names\n"
    "   open DB NAME             Open a new session on DB\n"
    "   patchset FILE            Write a patchset into FILE\n"
  );
}
#endif


/* Forward reference */
static int process_input(ShellState *p, FILE *in);
/*
** Implementation of the "readfile(X)" SQL function.  The entire content
** of the file named X is read and returned as a BLOB.  NULL is returned
** if the file does not exist or is unreadable.
................................................................................
    rc = 0;
  }else{
    rc = fwrite(z, 1, sqlite3_value_bytes(argv[1]), out);
  }
  fclose(out);
  sqlite3_result_int64(context, rc);
}

#if defined(SQLITE_ENABLE_SESSION)
/*
** Close a single OpenSession object and release all of its associated
** resources.
*/
static void session_close(OpenSession *pSession){
  int i;
  sqlite3session_delete(pSession->p);
  sqlite3_free(pSession->zName);
  for(i=0; i<pSession->nFilter; i++){
    sqlite3_free(pSession->azFilter[i]);
  }
  sqlite3_free(pSession->azFilter);
  memset(pSession, 0, sizeof(OpenSession));
}
#endif

/*
** Close all OpenSession objects and release all assocaited resources.
*/
static void session_close_all(ShellState *p){
#if defined(SQLITE_ENABLE_SESSION)
  int i;
  for(i=0; i<p->nSession; i++){
    session_close(&p->aSession[i]);
  }
  p->nSession = 0;
#endif
}

/*
** Implementation of the xFilter function for an open session.  Omit
** any tables named by ".session filter" but let all other table through.
*/
#if defined(SQLITE_ENABLE_SESSION)
static int session_filter(void *pCtx, const char *zTab){
  OpenSession *pSession = (OpenSession*)pCtx;
  int i;
  for(i=0; i<pSession->nFilter; i++){
    if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
  }
  return 1;
}
#endif

/*
** Make sure the database is open.  If it is not, then open it.  If
** the database fails to open, print an error message and exit.
*/
static void open_db(ShellState *p, int keepAlive){
  if( p->db==0 ){
................................................................................
    char *zNewFilename = 0;
    p->db = 0;
    if( nArg>=2 ){
      p->zDbFilename = zNewFilename = sqlite3_mprintf("%s", azArg[1]);
    }
    open_db(p, 1);
    if( p->db!=0 ){
      session_close_all(p);
      sqlite3_close(savedDb);
      sqlite3_free(p->zFreeOnClose);
      p->zFreeOnClose = zNewFilename;
    }else{
      sqlite3_free(zNewFilename);
      p->db = savedDb;
      p->zDbFilename = zSavedFilename;
................................................................................
    }else if( rc != SQLITE_OK ){
      fprintf(stderr,"Error: querying schema information\n");
      rc = 1;
    }else{
      rc = 0;
    }
  }else

#if defined(SQLITE_ENABLE_SESSION)
  if( c=='s' && strncmp(azArg[0],"session",n)==0 && n>=3 ){
    OpenSession *pSession = &p->aSession[0];
    char **azCmd = &azArg[1];
    int iSes = 0;
    int nCmd = nArg - 1;
    int i;
    if( nArg<=1 ) goto session_syntax_error;
    open_db(p, 0);
    if( nArg>=3 ){
      for(iSes=0; iSes<p->nSession; iSes++){
        if( strcmp(p->aSession[iSes].zName, azArg[1])==0 ) break;
      }
      if( iSes<p->nSession ){
        pSession = &p->aSession[iSes];
        azCmd++;
        nCmd--;
      }else{
        pSession = &p->aSession[0];
        iSes = 0;
      }
    }

    /* .session attach TABLE
    ** Invoke the sqlite3session_attach() interface to attach a particular
    ** table so that it is never filtered.
    */
    if( strcmp(azCmd[0],"attach")==0 ){
      if( nCmd!=2 ) goto session_syntax_error;
      if( pSession->p==0 ){
        session_not_open:
        fprintf(stderr, "ERROR: No sessions are open\n");
      }else{
        rc = sqlite3session_attach(pSession->p, azCmd[1]);
        if( rc ){
          fprintf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc);
          rc = 0;
        }
      }
    }else

    /* .session changeset FILE
    ** .session patchset FILE
    ** Write a changeset or patchset into a file.  The file is overwritten.
    */
    if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
      FILE *out = 0;
      if( nCmd!=2 ) goto session_syntax_error;
      if( pSession->p==0 ) goto session_not_open;
      out = fopen(azCmd[1], "wb");
      if( out==0 ){
        fprintf(stderr, "ERROR: cannot open \"%s\" for writing\n", azCmd[1]);
      }else{
        int szChng;
        void *pChng;
        if( azCmd[0][0]=='c' ){
          sqlite3session_changeset(pSession->p, &szChng, &pChng);
        }else{
          sqlite3session_patchset(pSession->p, &szChng, &pChng);
        }
        if( pChng 
          && fwrite(pChng, szChng, 1, out)!=1 ){
          fprintf(stderr, "ERROR: Failed to write entire %d-byte output\n",
                  szChng);
        }
        sqlite3_free(pChng);
        fclose(out);
      }
    }else

    /* .session close
    ** Close the identified session
    */
    if( strcmp(azCmd[0], "close")==0 ){
      if( nCmd!=1 ) goto session_syntax_error;
      if( p->nSession ){
        session_close(pSession);
        p->aSession[iSes] = p->aSession[--p->nSession];
      }
    }else

    /* .session enable ?BOOLEAN?
    ** Query or set the enable flag
    */
    if( strcmp(azCmd[0], "enable")==0 ){
      int ii;
      if( nCmd>2 ) goto session_syntax_error;
      ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
      if( p->nSession ){
        ii = sqlite3session_enable(pSession->p, ii);
        fprintf(p->out, "session %s enable flag = %d\n", pSession->zName, ii);
      }
    }else

    /* .session filter GLOB ....
    ** Set a list of GLOB patterns of table names to be excluded.
    */
    if( strcmp(azCmd[0], "filter")==0 ){
      int ii, nByte;
      if( nCmd<2 ) goto session_syntax_error;
      if( p->nSession ){
        for(ii=0; ii<pSession->nFilter; ii++){
          sqlite3_free(pSession->azFilter[ii]);
        }
        sqlite3_free(pSession->azFilter);
        nByte = sizeof(pSession->azFilter[0])*(nCmd-1);
        pSession->azFilter = sqlite3_malloc( nByte );
        if( pSession->azFilter==0 ){
          fprintf(stderr, "Error: out or memory\n");
          exit(1);
        }
        for(ii=1; ii<nCmd; ii++){
          pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]); 
        }
        pSession->nFilter = ii-1;
      }
    }else

    /* .session indirect ?BOOLEAN?
    ** Query or set the indirect flag
    */
    if( strcmp(azCmd[0], "indirect")==0 ){
      int ii;
      if( nCmd>2 ) goto session_syntax_error;
      ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
      if( p->nSession ){
        ii = sqlite3session_indirect(pSession->p, ii);
        fprintf(p->out, "session %s indirect flag = %d\n", pSession->zName,ii);
      }
    }else

    /* .session isempty
    ** Determine if the session is empty
    */
    if( strcmp(azCmd[0], "isempty")==0 ){
      int ii;
      if( nCmd!=1 ) goto session_syntax_error;
      if( p->nSession ){
        ii = sqlite3session_isempty(pSession->p);
        fprintf(p->out, "session %s isempty flag = %d\n", pSession->zName, ii);
      }
    }else

    /* .session list
    ** List all currently open sessions
    */
    if( strcmp(azCmd[0],"list")==0 ){
      for(i=0; i<p->nSession; i++){
        fprintf(p->out, "%d %s\n", i, p->aSession[i].zName);
      }
    }else

    /* .session open DB NAME
    ** Open a new session called NAME on the attached database DB.
    ** DB is normally "main".
    */
    if( strcmp(azCmd[0],"open")==0 ){
      char *zName;
      if( nCmd!=3 ) goto session_syntax_error;
      zName = azCmd[2];
      if( zName[0]==0 ) goto session_syntax_error;
      for(i=0; i<p->nSession; i++){
        if( strcmp(p->aSession[i].zName,zName)==0 ){
          fprintf(stderr, "Session \"%s\" already exists\n", zName);
          goto meta_command_exit;
        }
      }
      if( p->nSession>=ArraySize(p->aSession) ){
        fprintf(stderr, "Maximum of %d sessions\n", ArraySize(p->aSession));
        goto meta_command_exit;
      }
      pSession = &p->aSession[p->nSession];
      rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
      if( rc ){
        fprintf(stderr, "Cannot open session: error code=%d\n", rc);
        rc = 0;
        goto meta_command_exit;
      }
      pSession->nFilter = 0;
      sqlite3session_table_filter(pSession->p, session_filter, pSession);
      p->nSession++;
      pSession->zName = sqlite3_mprintf("%s", zName);
    }else
    /* If no command name matches, show a syntax error */
    session_syntax_error:
    session_help(p);
  }else
#endif

#ifdef SQLITE_DEBUG
  /* Undocumented commands for internal testing.  Subject to change
  ** without notice. */
  if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){
    if( strncmp(azArg[0]+9, "boolean", n-9)==0 ){
      int i, v;
................................................................................
      }
    }else{
      rc = process_input(&data, stdin);
    }
  }
  set_table_name(&data, 0);
  if( data.db ){
    session_close_all(&data);
    sqlite3_close(data.db);
  }
  sqlite3_free(data.zFreeOnClose); 
  return rc;
}