summaryrefslogtreecommitdiffstats
path: root/ext/session/changesetfuzz.c
blob: bbd72f6d9dde07770194c3c1b068fa2e7691fa9a (plain)
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
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
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
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
/*
** 2018-11-01
**
** 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 "changesetfuzz" command 
** line utility for fuzzing changeset blobs without corrupting them.
*/


/************************************************************************
** USAGE:
**
** This program may be invoked in two ways:
**
**   changesetfuzz INPUT
**   changesetfuzz INPUT SEED N
**
** Argument INPUT must be the name of a file containing a binary changeset.
** In the first form above, this program outputs a human-readable version
** of the same changeset. This is chiefly for debugging.
**
** As well as changesets, this program can also dump and fuzz patchsets.
** The term "changeset" is used for both patchsets and changesets from this
** point on.
**
** In the second form, arguments SEED and N must both be integers. In this
** case, this program writes N binary changesets to disk. Each output
** changeset is a slightly modified - "fuzzed" - version of the input. 
** The output changesets are written to files name "INPUT-$n", where $n is 
** an integer between 0 and N-1, inclusive. Output changesets are always
** well-formed. Parameter SEED is used to seed the PRNG - any two 
** invocations of this program with the same SEED and input changeset create
** the same N output changesets.
**
** The ways in which an input changeset may be fuzzed are as follows:
**
**   1. Any two values within the changeset may be exchanged.
**
**   2. Any TEXT, BLOB, INTEGER or REAL value within the changeset 
**      may have a single bit of its content flipped.
**
**   3. Any value within a changeset may be replaced by a pseudo-randomly
**      generated value.
**
** The above operations never set a PRIMARY KEY column to NULL. Nor do they
** set any value to "undefined", or replace any "undefined" value with
** another. Any such operation risks producing a changeset that is not 
** well-formed.
**
**   4. A single change may be duplicated.
**
**   5. A single change may be removed, so long as this does not mean that
**      there are zero changes following a table-header within the changeset.
**
**   6. A single change may have its type (INSERT, DELETE, UPDATE) changed.
**      If an INSERT is changed to a DELETE (or vice versa), the type is
**      simply changed - no other modifications are required. If an INSERT
**      or DELETE is changed to an UPDATE, then the single record is duplicated
**      (as both the old.* and new.* records of the new UPDATE change). If an
**      UPDATE is changed to a DELETE or INSERT, the new.* record is discarded
**      and any "undefined" fields replaced with pseudo-randomly generated
**      values.
**
**   7. An UPDATE change that modifies N table columns may be modified so
**      that it updates N-1 columns, so long as (N>1).
**
**   8. The "indirect" flag may be toggled for any change.
**
** Entire group of changes may also be operated on:
**
**   9. Duplicate an existing group.
**
**  10. Remove an existing group.
**
**  11. The positions of two groups may be exchanged.
**
** There are also schema changes:
**
**  12. A non-PK column may be added to a table. In this case a NULL 
**      value is appended to all records.
**
**  13. A PK column may be added to a table. In this case a non-NULL 
**      value is appended to all INSERT, DELETE and UPDATE old.* records.
**      An "undefined" is appended to new.* UPDATE records.
**
**  14. A column may be removed from a table, provided that it is not the
**      only PRIMARY KEY column in the table. In this case the corresponding
**      field is removed from all records. In cases where this leaves an UPDATE
**      with no non-PK, non-undefined fields, the entire change is removed.
*/

#include "sqlite3.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>

#define FUZZ_VALUE_SUB       1    /* Replace one value with a copy of another */
#define FUZZ_VALUE_MOD       2    /* Modify content by 1 bit */
#define FUZZ_VALUE_RND       3    /* Replace with pseudo-random value */

#define FUZZ_CHANGE_DUP      4    /* Duplicate an existing change */
#define FUZZ_CHANGE_DEL      5    /* Completely remove one change */
#define FUZZ_CHANGE_TYPE     6    /* Change the type of one change */
#define FUZZ_CHANGE_FIELD    7    /* Change an UPDATE to modify fewer columns */
#define FUZZ_CHANGE_INDIRECT 8    /* Toggle the "indirect" flag of a change */

#define FUZZ_GROUP_DUP       9    /* Duplicate a change group */
#define FUZZ_GROUP_DEL      10    /* Delete an entire change group */
#define FUZZ_GROUP_SWAP     11    /* Exchange the position of two groups */

#define FUZZ_COLUMN_ADD     12     /* Add column to table definition */
#define FUZZ_COLUMN_ADDPK   13     /* Add PK column to table definition */
#define FUZZ_COLUMN_DEL     14     /* Remove column from table definition */



typedef unsigned char u8;
typedef sqlite3_uint64 u64;
typedef sqlite3_int64 i64;
typedef unsigned int u32;

/*
** Show a usage message on stderr then quit.
*/
static void usage(const char *argv0){
  fprintf(stderr, "Usage: %s FILENAME ?SEED N?\n", argv0);
  exit(1);
}

/*
** Read the content of a disk file into an in-memory buffer
*/
static void fuzzReadFile(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;
}

/* 
** Write the contents of buffer pBuf, size nBuf bytes, into file zFilename
** on disk. zFilename, if it already exists, is clobbered.
*/
static void fuzzWriteFile(const char *zFilename, void *pBuf, int nBuf){
  FILE *f;
  f = fopen(zFilename, "wb");
  if( f==0 ){
    fprintf(stderr, "cannot open \"%s\" for writing\n", zFilename);
    exit(1);
  }
  if( fwrite(pBuf, nBuf, 1, f)!=1 ){
    fprintf(stderr, "cannot write to \"%s\"\n", zFilename);
    exit(1);
  }
  fclose(f);
}

static int fuzzCorrupt(){
  return SQLITE_CORRUPT;
}

/*************************************************************************
** The following block is a copy of the implementation of SQLite function
** sqlite3_randomness. This version has two important differences:
**
**   1. It always uses the same seed. So the sequence of random data output
**      is the same for every run of the program.
**
**   2. It is not threadsafe.
*/
static struct sqlite3PrngType {
  unsigned char i, j;             /* State variables */
  unsigned char s[256];           /* State variables */
} sqlite3Prng = {
    0xAF, 0x28,
  {
    0x71, 0xF5, 0xB4, 0x6E, 0x80, 0xAB, 0x1D, 0xB8, 
    0xFB, 0xB7, 0x49, 0xBF, 0xFF, 0x72, 0x2D, 0x14, 
    0x79, 0x09, 0xE3, 0x78, 0x76, 0xB0, 0x2C, 0x0A, 
    0x8E, 0x23, 0xEE, 0xDF, 0xE0, 0x9A, 0x2F, 0x67, 
    0xE1, 0xBE, 0x0E, 0xA7, 0x08, 0x97, 0xEB, 0x77, 
    0x78, 0xBA, 0x9D, 0xCA, 0x49, 0x4C, 0x60, 0x9A, 
    0xF6, 0xBD, 0xDA, 0x7F, 0xBC, 0x48, 0x58, 0x52, 
    0xE5, 0xCD, 0x83, 0x72, 0x23, 0x52, 0xFF, 0x6D, 
    0xEF, 0x0F, 0x82, 0x29, 0xA0, 0x83, 0x3F, 0x7D, 
    0xA4, 0x88, 0x31, 0xE7, 0x88, 0x92, 0x3B, 0x9B, 
    0x3B, 0x2C, 0xC2, 0x4C, 0x71, 0xA2, 0xB0, 0xEA, 
    0x36, 0xD0, 0x00, 0xF1, 0xD3, 0x39, 0x17, 0x5D, 
    0x2A, 0x7A, 0xE4, 0xAD, 0xE1, 0x64, 0xCE, 0x0F, 
    0x9C, 0xD9, 0xF5, 0xED, 0xB0, 0x22, 0x5E, 0x62, 
    0x97, 0x02, 0xA3, 0x8C, 0x67, 0x80, 0xFC, 0x88, 
    0x14, 0x0B, 0x15, 0x10, 0x0F, 0xC7, 0x40, 0xD4, 
    0xF1, 0xF9, 0x0E, 0x1A, 0xCE, 0xB9, 0x1E, 0xA1, 
    0x72, 0x8E, 0xD7, 0x78, 0x39, 0xCD, 0xF4, 0x5D, 
    0x2A, 0x59, 0x26, 0x34, 0xF2, 0x73, 0x0B, 0xA0, 
    0x02, 0x51, 0x2C, 0x03, 0xA3, 0xA7, 0x43, 0x13, 
    0xE8, 0x98, 0x2B, 0xD2, 0x53, 0xF8, 0xEE, 0x91, 
    0x7D, 0xE7, 0xE3, 0xDA, 0xD5, 0xBB, 0xC0, 0x92, 
    0x9D, 0x98, 0x01, 0x2C, 0xF9, 0xB9, 0xA0, 0xEB, 
    0xCF, 0x32, 0xFA, 0x01, 0x49, 0xA5, 0x1D, 0x9A, 
    0x76, 0x86, 0x3F, 0x40, 0xD4, 0x89, 0x8F, 0x9C, 
    0xE2, 0xE3, 0x11, 0x31, 0x37, 0xB2, 0x49, 0x28, 
    0x35, 0xC0, 0x99, 0xB6, 0xD0, 0xBC, 0x66, 0x35, 
    0xF7, 0x83, 0x5B, 0xD7, 0x37, 0x1A, 0x2B, 0x18, 
    0xA6, 0xFF, 0x8D, 0x7C, 0x81, 0xA8, 0xFC, 0x9E, 
    0xC4, 0xEC, 0x80, 0xD0, 0x98, 0xA7, 0x76, 0xCC, 
    0x9C, 0x2F, 0x7B, 0xFF, 0x8E, 0x0E, 0xBB, 0x90, 
    0xAE, 0x13, 0x06, 0xF5, 0x1C, 0x4E, 0x52, 0xF7
  }
};

/* 
** Generate and return single random byte 
*/
static unsigned char fuzzRandomByte(void){
  unsigned char t;
  sqlite3Prng.i++;
  t = sqlite3Prng.s[sqlite3Prng.i];
  sqlite3Prng.j += t;
  sqlite3Prng.s[sqlite3Prng.i] = sqlite3Prng.s[sqlite3Prng.j];
  sqlite3Prng.s[sqlite3Prng.j] = t;
  t += sqlite3Prng.s[sqlite3Prng.i];
  return sqlite3Prng.s[t];
}

/*
** Return N random bytes.
*/
static void fuzzRandomBlob(int nBuf, unsigned char *zBuf){
  int i;
  for(i=0; i<nBuf; i++){
    zBuf[i] = fuzzRandomByte();
  }
}

/*
** Return a random integer between 0 and nRange (not inclusive).
*/
static unsigned int fuzzRandomInt(unsigned int nRange){
  unsigned int ret;
  assert( nRange>0 );
  fuzzRandomBlob(sizeof(ret), (unsigned char*)&ret);
  return (ret % nRange);
}

static u64 fuzzRandomU64(){
  u64 ret;
  fuzzRandomBlob(sizeof(ret), (unsigned char*)&ret);
  return ret;
}

static void fuzzRandomSeed(unsigned int iSeed){
  int i;
  for(i=0; i<256; i+=4){
    sqlite3Prng.s[i] ^= ((iSeed >> 24) & 0xFF);
    sqlite3Prng.s[i+1] ^= ((iSeed >> 16) & 0xFF);
    sqlite3Prng.s[i+2] ^= ((iSeed >>  8) & 0xFF);
    sqlite3Prng.s[i+3] ^= ((iSeed >>  0) & 0xFF);
  }
}
/*
** End of code for generating pseudo-random values.
*************************************************************************/

typedef struct FuzzChangeset FuzzChangeset;
typedef struct FuzzChangesetGroup FuzzChangesetGroup;
typedef struct FuzzChange FuzzChange;

/* 
** Object containing partially parsed changeset.
*/
struct FuzzChangeset {
  int bPatchset;                  /* True for a patchset */
  FuzzChangesetGroup **apGroup;   /* Array of groups in changeset */
  int nGroup;                     /* Number of items in list pGroup */
  u8 **apVal;                     /* Array of all values in changeset */
  int nVal;                       /* Number of used slots in apVal[] */
  int nChange;                    /* Number of changes in changeset */
  int nUpdate;                    /* Number of UPDATE changes in changeset */
};

/* 
** There is one object of this type for each change-group (table header)
** in the input changeset.
*/
struct FuzzChangesetGroup {
  const char *zTab;               /* Name of table */
  int nCol;                       /* Number of columns in table */
  u8 *aPK;                        /* PK array for this table */
  u8 *aChange;                    /* Buffer containing array of changes */
  int szChange;                   /* Size of buffer aChange[] in bytes */
  int nChange;                    /* Number of changes in buffer aChange[] */
};

/*
** Description of a fuzz change to be applied to a changeset.
*/
struct FuzzChange {
  int eType;                      /* One of the FUZZ_* constants above */
  int iChange;                    /* Change or UPDATE to modify */
  int iGroup;                     /* Group to modify */
  int iDelete;                    /* Field to remove (FUZZ_COLUMN_DEL) */
  u8 *pSub1;                      /* Replace this value with pSub2 */
  u8 *pSub2;                      /* And this one with pSub1 */
  u8 aSub[128];                   /* Buffer for substitute value */
  int iCurrent;                   /* Current change number */
};

/*
** Allocate and return nByte bytes of zeroed memory.
*/
static void *fuzzMalloc(sqlite3_int64 nByte){
  void *pRet = sqlite3_malloc64(nByte);
  if( pRet ){
    memset(pRet, 0, (size_t)nByte);
  }
  return pRet;
}

/*
** Free the buffer indicated by the first argument. This function is used
** to free buffers allocated by fuzzMalloc().
*/
static void fuzzFree(void *p){
  sqlite3_free(p);
}

/*
** Argument p points to a buffer containing an SQLite varint that, assuming the
** input is not corrupt, may be between 0 and 0x7FFFFFFF, inclusive. Before
** returning, this function sets (*pnVal) to the value of that varint, and
** returns the number of bytes of space that it takes up.
*/
static int fuzzGetVarint(u8 *p, int *pnVal){
  int i;
  sqlite3_uint64 nVal = 0;
  for(i=0; i<9; i++){
    nVal = (nVal<<7) + (p[i] & 0x7F);
    if( (p[i] & 0x80)==0 ){
      i++;
      break;
    }
  }
  *pnVal = (int)nVal;
  return i;
}

/*
** Write value nVal into the buffer indicated by argument p as an SQLite
** varint. nVal is guaranteed to be between 0 and (2^21-1), inclusive.
** Return the number of bytes written to buffer p.
*/
static int fuzzPutVarint(u8 *p, int nVal){
  assert( nVal>0 && nVal<2097152 );
  if( nVal<128 ){
    p[0] = (u8)nVal;
    return 1;
  }
  if( nVal<16384 ){
    p[0] = ((nVal >> 7) & 0x7F) | 0x80;
    p[1] = (nVal & 0x7F);
    return 2;
  }

  p[0] = ((nVal >> 14) & 0x7F) | 0x80;
  p[1] = ((nVal >> 7) & 0x7F) | 0x80;
  p[2] = (nVal & 0x7F);
  return 3;
}

/*
** Read a 64-bit big-endian integer value from buffer aRec[]. Return
** the value read.
*/
static i64 fuzzGetI64(u8 *aRec){
  return (i64)(
      (((u64)aRec[0]) << 56)
    + (((u64)aRec[1]) << 48)
    + (((u64)aRec[2]) << 40)
    + (((u64)aRec[3]) << 32)
    + (((u64)aRec[4]) << 24)
    + (((u64)aRec[5]) << 16)
    + (((u64)aRec[6]) <<  8)
    + (((u64)aRec[7]) <<  0)
  );
}

/*
** Write value iVal to buffer aRec[] as an unsigned 64-bit big-endian integer.
*/
static void fuzzPutU64(u8 *aRec, u64 iVal){
  aRec[0] = (iVal>>56) & 0xFF;
  aRec[1] = (iVal>>48) & 0xFF;
  aRec[2] = (iVal>>40) & 0xFF;
  aRec[3] = (iVal>>32) & 0xFF;
  aRec[4] = (iVal>>24) & 0xFF;
  aRec[5] = (iVal>>16) & 0xFF;
  aRec[6] = (iVal>> 8) & 0xFF;
  aRec[7] = (iVal)     & 0xFF;
}

/*
** Parse a single table-header from the input. Allocate a new change-group
** object with the results. Return SQLITE_OK if successful, or an error code
** otherwise.
*/
static int fuzzParseHeader(
  FuzzChangeset *pParse,          /* Changeset parse object */
  u8 **ppHdr,                     /* IN/OUT: Iterator */
  u8 *pEnd,                       /* 1 byte past EOF */
  FuzzChangesetGroup **ppGrp      /* OUT: New change-group object */
){
  int rc = SQLITE_OK;
  FuzzChangesetGroup *pGrp;
  u8 cHdr = (pParse->bPatchset ? 'P' : 'T');

  assert( pEnd>(*ppHdr) );
  pGrp = (FuzzChangesetGroup*)fuzzMalloc(sizeof(FuzzChangesetGroup));
  if( !pGrp ){
    rc = SQLITE_NOMEM;
  }else{
    u8 *p = *ppHdr;
    if( p[0]!=cHdr ){
      rc = fuzzCorrupt();
    }else{
      p++;
      p += fuzzGetVarint(p, &pGrp->nCol);
      pGrp->aPK = p;
      p += pGrp->nCol;
      pGrp->zTab = (const char*)p;
      p = &p[strlen((const char*)p)+1];

      if( p>=pEnd ){
        rc = fuzzCorrupt();
      }
    }
    *ppHdr = p;
  }

  if( rc!=SQLITE_OK ){
    fuzzFree(pGrp);
    pGrp = 0;
  }

  *ppGrp = pGrp;
  return rc;
}

/*
** Argument p points to a buffer containing a single changeset-record value. 
** This function attempts to determine the size of the value in bytes. If
** successful, it sets (*pSz) to the size and returns SQLITE_OK. Or, if the
** buffer does not contain a valid value, SQLITE_CORRUPT is returned and
** the final value of (*pSz) is undefined.
*/
static int fuzzChangeSize(u8 *p, int *pSz){
  u8 eType = p[0];
  switch( eType ){
    case 0x00:                    /* undefined */
    case 0x05:                    /* null */
      *pSz = 1;
      break;

    case 0x01:                    /* integer */
    case 0x02:                    /* real */
      *pSz = 9;
      break;

    case 0x03:                    /* text */
    case 0x04: {                  /* blob */
      int nTxt;
      int sz;
      sz = fuzzGetVarint(&p[1], &nTxt);
      *pSz = 1 + sz + nTxt;
      break;
    }

    default:
      return fuzzCorrupt();
  }
  return SQLITE_OK;
}

/*
** When this function is called, (*ppRec) points to the start of a 
** record in a changeset being parsed. This function adds entries
** to the pParse->apVal[] array for all values and advances (*ppRec) 
** to one byte past the end of the record. Argument pEnd points to
** one byte past the end of the input changeset.
**
** Argument bPkOnly is true if the record being parsed is part of
** a DELETE record in a patchset. In this case, all non-primary-key
** fields have been omitted from the record.
**
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
*/
static int fuzzParseRecord(
  u8 **ppRec,                     /* IN/OUT: Iterator */
  u8 *pEnd,                       /* One byte after end of input data */
  FuzzChangeset *pParse,          /* Changeset parse context */
  int bPkOnly                     /* True if non-PK fields omitted */
){
  int rc = SQLITE_OK;
  FuzzChangesetGroup *pGrp = pParse->apGroup[pParse->nGroup-1];
  int i;
  u8 *p = *ppRec;

  for(i=0; rc==SQLITE_OK && i<pGrp->nCol; i++){
    if( bPkOnly==0 || pGrp->aPK[i] ){
      int sz;
      if( p>=pEnd ) break;
      if( (pParse->nVal & (pParse->nVal-1))==0 ){
        int nNew = pParse->nVal ? pParse->nVal*2 : 4;
        u8 **apNew = (u8**)sqlite3_realloc(pParse->apVal, nNew*sizeof(u8*));
        if( apNew==0 ) return SQLITE_NOMEM;
        pParse->apVal = apNew;
      }
      pParse->apVal[pParse->nVal++] = p;
      rc = fuzzChangeSize(p, &sz);
      p += sz;
    }
  }

  if( rc==SQLITE_OK && i<pGrp->nCol ){
    rc = fuzzCorrupt();
  }

  *ppRec = p;
  return rc;
}

/*
** Parse the array of changes starting at (*ppData) and add entries for
** all values to the pParse->apVal[] array. Argument pEnd points to one byte
** past the end of the input changeset. If successful, set (*ppData) to point
** to one byte past the end of the change array and return SQLITE_OK.
** Otherwise, return an SQLite error code. The final value of (*ppData) is
** undefined in this case.
*/
static int fuzzParseChanges(u8 **ppData, u8 *pEnd, FuzzChangeset *pParse){
  u8 cHdr = (pParse->bPatchset ? 'P' : 'T');
  FuzzChangesetGroup *pGrp = pParse->apGroup[pParse->nGroup-1];
  int rc = SQLITE_OK;
  u8 *p = *ppData;

  pGrp->aChange = p;
  while( rc==SQLITE_OK && p<pEnd && p[0]!=cHdr ){
    u8 eOp = p[0];
    u8 bIndirect = p[1];

    p += 2;
    if( eOp==SQLITE_UPDATE ){
      pParse->nUpdate++;
      if( pParse->bPatchset==0 ){
        rc = fuzzParseRecord(&p, pEnd, pParse, 0);
      }
    }else if( eOp!=SQLITE_INSERT && eOp!=SQLITE_DELETE ){
      rc = fuzzCorrupt();
    }
    if( rc==SQLITE_OK ){
      int bPkOnly = (eOp==SQLITE_DELETE && pParse->bPatchset);
      rc = fuzzParseRecord(&p, pEnd, pParse, bPkOnly);
    }
    pGrp->nChange++;
    pParse->nChange++;
  }
  pGrp->szChange = p - pGrp->aChange;

  *ppData = p;
  return rc;
}

/*
** Parse the changeset stored in buffer pChangeset (nChangeset bytes in
** size). If successful, write the results into (*pParse) and return
** SQLITE_OK. Or, if an error occurs, return an SQLite error code. The
** final state of (*pParse) is undefined in this case.
*/
static int fuzzParseChangeset(
  u8 *pChangeset,                 /* Buffer containing changeset */
  int nChangeset,                 /* Size of buffer in bytes */
  FuzzChangeset *pParse           /* OUT: Results of parse */
){
  u8 *pEnd = &pChangeset[nChangeset];
  u8 *p = pChangeset;
  int rc = SQLITE_OK;

  memset(pParse, 0, sizeof(FuzzChangeset));
  if( nChangeset>0 ){
    pParse->bPatchset = (pChangeset[0]=='P');
  }

  while( rc==SQLITE_OK && p<pEnd ){
    FuzzChangesetGroup *pGrp = 0;

    /* Read a table-header from the changeset */
    rc = fuzzParseHeader(pParse, &p, pEnd, &pGrp);
    assert( (rc==SQLITE_OK)==(pGrp!=0) );

    /* If the table-header was successfully parsed, add the new change-group
    ** to the array and parse the associated changes. */
    if( rc==SQLITE_OK ){
      FuzzChangesetGroup **apNew = (FuzzChangesetGroup**)sqlite3_realloc64(
          pParse->apGroup, sizeof(FuzzChangesetGroup*)*(pParse->nGroup+1)
      );
      if( apNew==0 ){
        rc = SQLITE_NOMEM;
      }else{
        apNew[pParse->nGroup] = pGrp;
        pParse->apGroup = apNew;
        pParse->nGroup++;
      }
      rc = fuzzParseChanges(&p, pEnd, pParse);
    }
  }

  return rc;
}

/*
** When this function is called, (*ppRec) points to the first byte of
** a record that is part of change-group pGrp. This function attempts
** to output a human-readable version of the record to stdout and advance
** (*ppRec) to point to the first byte past the end of the record before
** returning. If successful, SQLITE_OK is returned. Otherwise, an SQLite
** error code.
**
** If parameter bPkOnly is non-zero, then all non-primary-key fields have
** been omitted from the record. This occurs for records that are part
** of DELETE changes in patchsets.
*/
static int fuzzPrintRecord(FuzzChangesetGroup *pGrp, u8 **ppRec, int bPKOnly){
  int rc = SQLITE_OK;
  u8 *p = *ppRec;
  int i;
  const char *zPre = " (";

  for(i=0; i<pGrp->nCol; i++){
    if( bPKOnly==0 || pGrp->aPK[i] ){
      u8 eType = p++[0];
      switch( eType ){
        case 0x00:                    /* undefined */
          printf("%sn/a", zPre);
          break;

        case 0x01: {                  /* integer */
          sqlite3_int64 iVal = 0;
          iVal = fuzzGetI64(p);
          printf("%s%lld", zPre, iVal);
          p += 8;
          break;
        }

        case 0x02: {                  /* real */
          sqlite3_int64 iVal = 0;
          double fVal = 0.0;
          iVal = fuzzGetI64(p);
          memcpy(&fVal, &iVal, 8);
          printf("%s%f", zPre, fVal);
          p += 8;
          break;
        }

        case 0x03:                    /* text */
        case 0x04: {                  /* blob */
          int nTxt;
          p += fuzzGetVarint(p, &nTxt);
          printf("%s%s", zPre, eType==0x03 ? "'" : "X'");
          for(i=0; i<nTxt; i++){
            if( eType==0x03 ){
              printf("%c", p[i]);
            }else{
              char aHex[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
                               '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
              };
              printf("%c", aHex[ p[i]>>4 ]);
              printf("%c", aHex[ p[i] & 0x0F ]);
            }
          }
          printf("'");
          p += nTxt;
          break;
        }

        case 0x05:                    /* null */
          printf("%sNULL", zPre);
          break;
      }
      zPre = ", ";
    }
  }
  printf(")");

  *ppRec = p;
  return rc;
}

/*
** Print a human-readable version of the table-header and all changes in the
** change-group passed as the second argument.
*/
static void fuzzPrintGroup(FuzzChangeset *pParse, FuzzChangesetGroup *pGrp){
  int i;
  u8 *p;

  /* The table header */
  printf("TABLE:  %s nCol=%d aPK=", pGrp->zTab, pGrp->nCol);
  for(i=0; i<pGrp->nCol; i++){
    printf("%d", (int)pGrp->aPK[i]);
  }
  printf("\n");

  /* The array of changes */
  p = pGrp->aChange;
  for(i=0; i<pGrp->nChange; i++){
    u8 eType = p[0];
    u8 bIndirect = p[1];
    printf("%s (ind=%d):",
        (eType==SQLITE_INSERT) ? "INSERT" :
        (eType==SQLITE_DELETE ? "DELETE" : "UPDATE"),
        bIndirect
    );
    p += 2;

    if( pParse->bPatchset==0 && eType==SQLITE_UPDATE ){
      fuzzPrintRecord(pGrp, &p, 0);
    }
    fuzzPrintRecord(pGrp, &p, eType==SQLITE_DELETE && pParse->bPatchset);
    printf("\n");
  }
}

/*
** Initialize the object passed as the second parameter with details
** of the change that will be attempted (type of change, to which part of the
** changeset it applies etc.). If successful, return SQLITE_OK. Or, if an
** error occurs, return an SQLite error code. 
**
** If a negative value is returned, then the selected change would have
** produced a non-well-formed changeset. In this case the caller should
** call this function again.
*/
static int fuzzSelectChange(FuzzChangeset *pParse, FuzzChange *pChange){
  int iSub;

  memset(pChange, 0, sizeof(FuzzChange));
  pChange->eType = fuzzRandomInt(FUZZ_COLUMN_DEL) + 1;

  assert( pChange->eType==FUZZ_VALUE_SUB
       || pChange->eType==FUZZ_VALUE_MOD
       || pChange->eType==FUZZ_VALUE_RND
       || pChange->eType==FUZZ_CHANGE_DUP
       || pChange->eType==FUZZ_CHANGE_DEL
       || pChange->eType==FUZZ_CHANGE_TYPE
       || pChange->eType==FUZZ_CHANGE_FIELD
       || pChange->eType==FUZZ_CHANGE_INDIRECT
       || pChange->eType==FUZZ_GROUP_DUP
       || pChange->eType==FUZZ_GROUP_DEL
       || pChange->eType==FUZZ_GROUP_SWAP
       || pChange->eType==FUZZ_COLUMN_ADD
       || pChange->eType==FUZZ_COLUMN_ADDPK
       || pChange->eType==FUZZ_COLUMN_DEL
  );

  pChange->iGroup = fuzzRandomInt(pParse->nGroup);
  pChange->iChange = fuzzRandomInt(pParse->nChange);
  if( pChange->eType==FUZZ_CHANGE_FIELD ){
    if( pParse->nUpdate==0 ) return -1;
    pChange->iChange = fuzzRandomInt(pParse->nUpdate);
  }

  pChange->iDelete = -1;
  if( pChange->eType==FUZZ_COLUMN_DEL ){
    FuzzChangesetGroup *pGrp = pParse->apGroup[pChange->iGroup];
    int i;
    pChange->iDelete = fuzzRandomInt(pGrp->nCol);
    for(i=pGrp->nCol-1; i>=0; i--){
      if( pGrp->aPK[i] && pChange->iDelete!=i ) break;
    }
    if( i<0 ) return -1;
  }

  if( pChange->eType==FUZZ_GROUP_SWAP ){
    FuzzChangesetGroup *pGrp;
    int iGrp = pChange->iGroup;
    if( pParse->nGroup==1 ) return -1;
    while( iGrp==pChange->iGroup ){
      iGrp = fuzzRandomInt(pParse->nGroup);
    }
    pGrp = pParse->apGroup[pChange->iGroup];
    pParse->apGroup[pChange->iGroup] = pParse->apGroup[iGrp];
    pParse->apGroup[iGrp] = pGrp;
  }

  if( pChange->eType==FUZZ_VALUE_SUB 
   || pChange->eType==FUZZ_VALUE_MOD 
   || pChange->eType==FUZZ_VALUE_RND 
  ){
    iSub = fuzzRandomInt(pParse->nVal);
    pChange->pSub1 = pParse->apVal[iSub];
    if( pChange->eType==FUZZ_VALUE_SUB ){
      iSub = fuzzRandomInt(pParse->nVal);
      pChange->pSub2 = pParse->apVal[iSub];
    }else{
      pChange->pSub2 = pChange->aSub;
    }

    if( pChange->eType==FUZZ_VALUE_RND ){
      pChange->aSub[0] = (u8)(fuzzRandomInt(5) + 1);
      switch( pChange->aSub[0] ){
        case 0x01: {                  /* integer */
          u64 iVal = fuzzRandomU64();
          fuzzPutU64(&pChange->aSub[1], iVal);
          break;
        }

        case 0x02: {                  /* real */
          u64 iVal1 = fuzzRandomU64();
          u64 iVal2 = fuzzRandomU64();
          double d = (double)iVal1 / (double)iVal2;
          memcpy(&iVal1, &d, sizeof(iVal1));
          fuzzPutU64(&pChange->aSub[1], iVal1);
          break;
        }

        case 0x03:                    /* text */
        case 0x04: {                  /* blob */
          int nByte = fuzzRandomInt(48);
          pChange->aSub[1] = (u8)nByte;
          fuzzRandomBlob(nByte, &pChange->aSub[2]);
          if( pChange->aSub[0]==0x03 ){
            int i;
            for(i=0; i<nByte; i++){
              pChange->aSub[2+i] &= 0x7F;
            }
          }
          break;
        }
      }
    }
    if( pChange->eType==FUZZ_VALUE_MOD ){
      int sz;
      int iMod = -1;
      fuzzChangeSize(pChange->pSub1, &sz);
      memcpy(pChange->aSub, pChange->pSub1, sz);
      switch( pChange->aSub[0] ){
        case 0x01:
        case 0x02:
          iMod = fuzzRandomInt(8) + 1;
          break;

        case 0x03:                    /* text */
        case 0x04: {                  /* blob */
          int nByte;
          int iFirst = 1 + fuzzGetVarint(&pChange->aSub[1], &nByte);
          if( nByte>0 ){
            iMod = fuzzRandomInt(nByte) + iFirst;
          }
          break;
        }
      }

      if( iMod>=0 ){
        u8 mask = (1 << fuzzRandomInt(8 - (pChange->aSub[0]==0x03)));
        pChange->aSub[iMod] ^= mask;
      }
    }
  }

  return SQLITE_OK;
}

/*
** Copy a single change from the input to the output changeset, making
** any modifications specified by (*pFuzz).
*/
static int fuzzCopyChange(
  FuzzChangeset *pParse,
  int iGrp,
  FuzzChange *pFuzz,
  u8 **pp, u8 **ppOut             /* IN/OUT: Input and output pointers */
){
  int bPS = pParse->bPatchset;
  FuzzChangesetGroup *pGrp = pParse->apGroup[iGrp];
  u8 *p = *pp;
  u8 *pOut = *ppOut;
  u8 eType = p++[0];
  int iRec;
  int nRec = ((eType==SQLITE_UPDATE && !bPS) ? 2 : 1);
  int iUndef = -1;
  int nUpdate = 0;

  u8 eNew = eType;
  if( pFuzz->iCurrent==pFuzz->iChange && pFuzz->eType==FUZZ_CHANGE_TYPE ){
    switch( eType ){
      case SQLITE_INSERT:
        eNew = SQLITE_DELETE;
        break;
      case SQLITE_DELETE:
        eNew = SQLITE_UPDATE;
        break;
      case SQLITE_UPDATE:
        eNew = SQLITE_INSERT;
        break;
    }
  }

  if( pFuzz->iCurrent==pFuzz->iChange 
   && pFuzz->eType==FUZZ_CHANGE_FIELD && eType==SQLITE_UPDATE
  ){
    int sz;
    int i;
    int nDef = 0;
    u8 *pCsr = p+1;
    for(i=0; i<pGrp->nCol; i++){
      if( pCsr[0] && pGrp->aPK[i]==0 ) nDef++;
      fuzzChangeSize(pCsr, &sz);
      pCsr += sz;
    }
    if( nDef<=1 ) return -1;
    nDef = fuzzRandomInt(nDef);
    pCsr = p+1;
    for(i=0; i<pGrp->nCol; i++){
      if( pCsr[0] && pGrp->aPK[i]==0 ){
        if( nDef==0 ) iUndef = i;
        nDef--;
      }
      fuzzChangeSize(pCsr, &sz);
      pCsr += sz;
    }
  }

  /* Copy the change type and indirect flag. If the fuzz mode is
  ** FUZZ_CHANGE_INDIRECT, and the current change is the one selected for
  ** fuzzing, invert the indirect flag.  */
  *(pOut++) = eNew;
  if( pFuzz->eType==FUZZ_CHANGE_INDIRECT && pFuzz->iCurrent==pFuzz->iChange ){
    *(pOut++) = !(*(p++));
  }else{
    *(pOut++) = *(p++);
  }

  for(iRec=0; iRec<nRec; iRec++){
    int i;

    /* Copy the next record from the output to the input.
    */
    for(i=0; i<pGrp->nCol; i++){
      int sz;
      u8 *pCopy = p;

      /* If this is a patchset, and the input is a DELETE, then the only
      ** fields present are the PK fields. So, if this is not a PK, skip to 
      ** the next column. If the current fuzz is FUZZ_CHANGE_TYPE, then
      ** write a randomly selected value to the output.  */
      if( bPS && eType==SQLITE_DELETE && pGrp->aPK[i]==0 ){
        if( eType!=eNew ){
          assert( eNew==SQLITE_UPDATE );
          do {
            pCopy = pParse->apVal[fuzzRandomInt(pParse->nVal)];
          }while( pCopy[0]==0x00 );
          fuzzChangeSize(pCopy, &sz);
          memcpy(pOut, pCopy, sz);
          pOut += sz;
        }
        continue;
      }

      if( p==pFuzz->pSub1 ){
        pCopy = pFuzz->pSub2;
      }else if( p==pFuzz->pSub2 ){
        pCopy = pFuzz->pSub1;
      }else if( i==iUndef ){
        pCopy = (u8*)"\0";
      }

      if( pCopy[0]==0x00 && eNew!=eType && eType==SQLITE_UPDATE && iRec==0 ){
        while( pCopy[0]==0x00 ){
          pCopy = pParse->apVal[fuzzRandomInt(pParse->nVal)];
        }
      }else if( p[0]==0x00 && pCopy[0]!=0x00 ){
        return -1;
      }else{
        if( pGrp->aPK[i]>0 && pCopy[0]==0x05 ) return -1;
      }

      if( (pFuzz->iGroup!=iGrp || i!=pFuzz->iDelete)
       && (eNew==eType || eType!=SQLITE_UPDATE || iRec==0)
       && (eNew==eType || eNew!=SQLITE_DELETE || !bPS || pGrp->aPK[i])
      ){
        fuzzChangeSize(pCopy, &sz);
        memcpy(pOut, pCopy, sz);
        pOut += sz;
        nUpdate += (pGrp->aPK[i]==0 && pCopy[0]!=0x00);
      }

      fuzzChangeSize(p, &sz);
      p += sz;
    }

    if( iGrp==pFuzz->iGroup ){
      if( pFuzz->eType==FUZZ_COLUMN_ADD ){
        if( !bPS || eType!=SQLITE_DELETE ) *(pOut++) = 0x05;
      }else if( pFuzz->eType==FUZZ_COLUMN_ADDPK ){
        if( iRec==1 ){
          *(pOut++) = 0x00;
        }else{
          u8 *pNew;
          int szNew;
          do {
            pNew = pParse->apVal[fuzzRandomInt(pParse->nVal)];
          }while( pNew[0]==0x00 || pNew[0]==0x05 );
          fuzzChangeSize(pNew, &szNew);
          memcpy(pOut, pNew, szNew);
          pOut += szNew;
        }
      }
    }
  }

  if( pFuzz->iCurrent==pFuzz->iChange ){
    if( pFuzz->eType==FUZZ_CHANGE_DUP ){
      int nByte = pOut - (*ppOut);
      memcpy(pOut, *ppOut, nByte);
      pOut += nByte;
    }

    if( pFuzz->eType==FUZZ_CHANGE_DEL ){
      pOut = *ppOut;
    }
    if( eNew!=eType && eNew==SQLITE_UPDATE && !bPS ){
      int i;
      u8 *pCsr = (*ppOut) + 2;
      for(i=0; i<pGrp->nCol; i++){
        int sz;
        u8 *pCopy = pCsr;
        if( pGrp->aPK[i] ) pCopy = (u8*)"\0";
        fuzzChangeSize(pCopy, &sz);
        memcpy(pOut, pCopy, sz);
        pOut += sz;
        fuzzChangeSize(pCsr, &sz);
        pCsr += sz;
      }
    }
  }

  /* If a column is being deleted from this group, and this change was an 
  ** UPDATE, and there are now no non-PK, non-undefined columns in the 
  ** change, remove it altogether. */
  if( pFuzz->eType==FUZZ_COLUMN_DEL && pFuzz->iGroup==iGrp 
   && eType==SQLITE_UPDATE && nUpdate==0 
  ){
    pOut = *ppOut;
  }

  *pp = p;
  *ppOut = pOut;
  pFuzz->iCurrent += (eType==SQLITE_UPDATE || pFuzz->eType!=FUZZ_CHANGE_FIELD);
  return SQLITE_OK;
}

/*
** Fuzz the changeset parsed into object pParse and write the results 
** to file zOut on disk. Argument pBuf points to a buffer that is guaranteed
** to be large enough to hold the fuzzed changeset.
**
** Return SQLITE_OK if successful, or an SQLite error code if an error occurs.
*/
static int fuzzDoOneFuzz(
  const char *zOut,               /* Filename to write modified changeset to */
  u8 *pBuf,                       /* Buffer to use for modified changeset */
  FuzzChangeset *pParse           /* Parse of input changeset */
){
  FuzzChange change;
  int iGrp;
  int rc = -1;

  while( rc<0 ){
    u8 *pOut = pBuf;
    rc = fuzzSelectChange(pParse, &change);
    for(iGrp=0; rc==SQLITE_OK && iGrp<pParse->nGroup; iGrp++){
      FuzzChangesetGroup *pGrp = pParse->apGroup[iGrp];
      int nTab = strlen(pGrp->zTab) + 1;
      int j;
      int nRep = 1;

      /* If this is the group to delete for a FUZZ_GROUP_DEL change, jump to
      ** the next group. Unless this is the only group in the changeset - in
      ** that case this change cannot be applied.
      **
      ** Or, if this is a FUZZ_GROUP_DUP, set nRep to 2 to output two
      ** copies of the group. */
      if( change.iGroup==iGrp ){
        if( change.eType==FUZZ_GROUP_DEL ){
          if( pParse->nGroup==1 ) rc = -1;
          continue;
        }
        else if( change.eType==FUZZ_GROUP_DUP ){
          nRep = 2;
        }
      }

      for(j=0; j<nRep; j++){
        int i;
        u8 *pSaved;
        u8 *p = pGrp->aChange;
        int nCol = pGrp->nCol;
        int iPKDel = 0;
        if( iGrp==change.iGroup ){
          if( change.eType==FUZZ_COLUMN_ADD 
           || change.eType==FUZZ_COLUMN_ADDPK 
          ){
            nCol++;
          }else if( change.eType==FUZZ_COLUMN_DEL ){
            nCol--;
            iPKDel = pGrp->aPK[change.iDelete];
          }
        }

        /* Output a table header */
        pOut++[0] = pParse->bPatchset ? 'P' : 'T';
        pOut += fuzzPutVarint(pOut, nCol);

        for(i=0; i<pGrp->nCol; i++){
          if( iGrp!=change.iGroup || i!=change.iDelete ){
            u8 v = pGrp->aPK[i];
            if( iPKDel && v>iPKDel ) v--;
            *(pOut++) = v;
          }
        }
        if( nCol>pGrp->nCol ){
          if( change.eType==FUZZ_COLUMN_ADD ){
            *(pOut++) = 0x00;
          }else{
            u8 max = 0;
            for(i=0; i<pGrp->nCol; i++){
              if( pGrp->aPK[i]>max ) max = pGrp->aPK[i];
            }
            *(pOut++) = max+1;
          }
        }
        memcpy(pOut, pGrp->zTab, nTab);
        pOut += nTab;

        /* Output the change array. */
        pSaved = pOut;
        for(i=0; rc==SQLITE_OK && i<pGrp->nChange; i++){
          rc = fuzzCopyChange(pParse, iGrp, &change, &p, &pOut);
        }
        if( pOut==pSaved ) rc = -1;
      }
    }
    if( rc==SQLITE_OK ){
      fuzzWriteFile(zOut, pBuf, pOut-pBuf);
    }
  }

  return rc;
}

int main(int argc, char **argv){
  int nRepeat = 0;                /* Number of output files */
  int iSeed = 0;                  /* Value of PRNG seed */
  const char *zInput;             /* Name of input file */
  void *pChangeset = 0;           /* Input changeset */
  int nChangeset = 0;             /* Size of input changeset in bytes */
  int i;                          /* Current output file */
  FuzzChangeset changeset;        /* Partially parsed changeset */
  int rc;
  u8 *pBuf = 0;

  if( argc!=4 && argc!=2 ) usage(argv[0]);
  zInput = argv[1];

  fuzzReadFile(zInput, &nChangeset, &pChangeset);
  rc = fuzzParseChangeset(pChangeset, nChangeset, &changeset);

  if( rc==SQLITE_OK ){
    if( argc==2 ){
      for(i=0; i<changeset.nGroup; i++){
        fuzzPrintGroup(&changeset, changeset.apGroup[i]);
      }
    }else{
      pBuf = (u8*)fuzzMalloc((sqlite3_int64)nChangeset*2 + 1024);
      if( pBuf==0 ){
        rc = SQLITE_NOMEM;
      }else{
        iSeed = atoi(argv[2]);
        nRepeat = atoi(argv[3]);
        fuzzRandomSeed((unsigned int)iSeed);
        for(i=0; rc==SQLITE_OK && i<nRepeat; i++){
          char *zOut = sqlite3_mprintf("%s-%d", zInput, i);
          rc = fuzzDoOneFuzz(zOut, pBuf, &changeset);
          sqlite3_free(zOut);
        }
        fuzzFree(pBuf);
      }
    }
  }

  if( rc!=SQLITE_OK ){
    fprintf(stderr, "error while processing changeset: %d\n", rc);
  }

  return rc;
}