Fossil SCM

Add the ability to show the difference between two SQLite database files.

drh 2024-11-15 14:14 trunk
Commit 27c81f1c223679ccc401d7abf604e2b986ac232ff877c3978b1101bf869e0cbf
+3 -1
--- src/diff.c
+++ src/diff.c
@@ -3004,11 +3004,13 @@
30043004
c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
30053005
&c.nTo, pCfg->diffFlags);
30063006
if( c.aFrom==0 || c.aTo==0 ){
30073007
fossil_free(c.aFrom);
30083008
fossil_free(c.aTo);
3009
- if( pOut ){
3009
+ if( sqldiff(pA_Blob, pB_Blob, pOut, pCfg) ){
3010
+ /* An SQL diff has been put into pOut */
3011
+ }else if( pOut ){
30103012
diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, pCfg->diffFlags);
30113013
}
30123014
return 0;
30133015
}
30143016
30153017
--- src/diff.c
+++ src/diff.c
@@ -3004,11 +3004,13 @@
3004 c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
3005 &c.nTo, pCfg->diffFlags);
3006 if( c.aFrom==0 || c.aTo==0 ){
3007 fossil_free(c.aFrom);
3008 fossil_free(c.aTo);
3009 if( pOut ){
 
 
3010 diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, pCfg->diffFlags);
3011 }
3012 return 0;
3013 }
3014
3015
--- src/diff.c
+++ src/diff.c
@@ -3004,11 +3004,13 @@
3004 c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
3005 &c.nTo, pCfg->diffFlags);
3006 if( c.aFrom==0 || c.aTo==0 ){
3007 fossil_free(c.aFrom);
3008 fossil_free(c.aTo);
3009 if( sqldiff(pA_Blob, pB_Blob, pOut, pCfg) ){
3010 /* An SQL diff has been put into pOut */
3011 }else if( pOut ){
3012 diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, pCfg->diffFlags);
3013 }
3014 return 0;
3015 }
3016
3017
+12
--- src/main.mk
+++ src/main.mk
@@ -132,10 +132,11 @@
132132
$(SRCDIR)/shun.c \
133133
$(SRCDIR)/sitemap.c \
134134
$(SRCDIR)/skins.c \
135135
$(SRCDIR)/smtp.c \
136136
$(SRCDIR)/sqlcmd.c \
137
+ $(SRCDIR)/sqldiff.c \
137138
$(SRCDIR)/stash.c \
138139
$(SRCDIR)/stat.c \
139140
$(SRCDIR)/statrep.c \
140141
$(SRCDIR)/style.c \
141142
$(SRCDIR)/sync.c \
@@ -396,10 +397,11 @@
396397
$(OBJDIR)/shun_.c \
397398
$(OBJDIR)/sitemap_.c \
398399
$(OBJDIR)/skins_.c \
399400
$(OBJDIR)/smtp_.c \
400401
$(OBJDIR)/sqlcmd_.c \
402
+ $(OBJDIR)/sqldiff_.c \
401403
$(OBJDIR)/stash_.c \
402404
$(OBJDIR)/stat_.c \
403405
$(OBJDIR)/statrep_.c \
404406
$(OBJDIR)/style_.c \
405407
$(OBJDIR)/sync_.c \
@@ -545,10 +547,11 @@
545547
$(OBJDIR)/shun.o \
546548
$(OBJDIR)/sitemap.o \
547549
$(OBJDIR)/skins.o \
548550
$(OBJDIR)/smtp.o \
549551
$(OBJDIR)/sqlcmd.o \
552
+ $(OBJDIR)/sqldiff.o \
550553
$(OBJDIR)/stash.o \
551554
$(OBJDIR)/stat.o \
552555
$(OBJDIR)/statrep.o \
553556
$(OBJDIR)/style.o \
554557
$(OBJDIR)/sync.o \
@@ -880,10 +883,11 @@
880883
$(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
881884
$(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
882885
$(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
883886
$(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \
884887
$(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h \
888
+ $(OBJDIR)/sqldiff_.c:$(OBJDIR)/sqldiff.h \
885889
$(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \
886890
$(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \
887891
$(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \
888892
$(OBJDIR)/style_.c:$(OBJDIR)/style.h \
889893
$(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \
@@ -1860,10 +1864,18 @@
18601864
18611865
$(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h
18621866
$(XTCC) -o $(OBJDIR)/sqlcmd.o -c $(OBJDIR)/sqlcmd_.c
18631867
18641868
$(OBJDIR)/sqlcmd.h: $(OBJDIR)/headers
1869
+
1870
+$(OBJDIR)/sqldiff_.c: $(SRCDIR)/sqldiff.c $(OBJDIR)/translate
1871
+ $(OBJDIR)/translate $(SRCDIR)/sqldiff.c >$@
1872
+
1873
+$(OBJDIR)/sqldiff.o: $(OBJDIR)/sqldiff_.c $(OBJDIR)/sqldiff.h $(SRCDIR)/config.h
1874
+ $(XTCC) -o $(OBJDIR)/sqldiff.o -c $(OBJDIR)/sqldiff_.c
1875
+
1876
+$(OBJDIR)/sqldiff.h: $(OBJDIR)/headers
18651877
18661878
$(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(OBJDIR)/translate
18671879
$(OBJDIR)/translate $(SRCDIR)/stash.c >$@
18681880
18691881
$(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h
18701882
18711883
ADDED src/sqldiff.c
--- src/main.mk
+++ src/main.mk
@@ -132,10 +132,11 @@
132 $(SRCDIR)/shun.c \
133 $(SRCDIR)/sitemap.c \
134 $(SRCDIR)/skins.c \
135 $(SRCDIR)/smtp.c \
136 $(SRCDIR)/sqlcmd.c \
 
137 $(SRCDIR)/stash.c \
138 $(SRCDIR)/stat.c \
139 $(SRCDIR)/statrep.c \
140 $(SRCDIR)/style.c \
141 $(SRCDIR)/sync.c \
@@ -396,10 +397,11 @@
396 $(OBJDIR)/shun_.c \
397 $(OBJDIR)/sitemap_.c \
398 $(OBJDIR)/skins_.c \
399 $(OBJDIR)/smtp_.c \
400 $(OBJDIR)/sqlcmd_.c \
 
401 $(OBJDIR)/stash_.c \
402 $(OBJDIR)/stat_.c \
403 $(OBJDIR)/statrep_.c \
404 $(OBJDIR)/style_.c \
405 $(OBJDIR)/sync_.c \
@@ -545,10 +547,11 @@
545 $(OBJDIR)/shun.o \
546 $(OBJDIR)/sitemap.o \
547 $(OBJDIR)/skins.o \
548 $(OBJDIR)/smtp.o \
549 $(OBJDIR)/sqlcmd.o \
 
550 $(OBJDIR)/stash.o \
551 $(OBJDIR)/stat.o \
552 $(OBJDIR)/statrep.o \
553 $(OBJDIR)/style.o \
554 $(OBJDIR)/sync.o \
@@ -880,10 +883,11 @@
880 $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
881 $(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
882 $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
883 $(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \
884 $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h \
 
885 $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \
886 $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \
887 $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \
888 $(OBJDIR)/style_.c:$(OBJDIR)/style.h \
889 $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \
@@ -1860,10 +1864,18 @@
1860
1861 $(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h
1862 $(XTCC) -o $(OBJDIR)/sqlcmd.o -c $(OBJDIR)/sqlcmd_.c
1863
1864 $(OBJDIR)/sqlcmd.h: $(OBJDIR)/headers
 
 
 
 
 
 
 
 
1865
1866 $(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(OBJDIR)/translate
1867 $(OBJDIR)/translate $(SRCDIR)/stash.c >$@
1868
1869 $(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h
1870
1871 DDED src/sqldiff.c
--- src/main.mk
+++ src/main.mk
@@ -132,10 +132,11 @@
132 $(SRCDIR)/shun.c \
133 $(SRCDIR)/sitemap.c \
134 $(SRCDIR)/skins.c \
135 $(SRCDIR)/smtp.c \
136 $(SRCDIR)/sqlcmd.c \
137 $(SRCDIR)/sqldiff.c \
138 $(SRCDIR)/stash.c \
139 $(SRCDIR)/stat.c \
140 $(SRCDIR)/statrep.c \
141 $(SRCDIR)/style.c \
142 $(SRCDIR)/sync.c \
@@ -396,10 +397,11 @@
397 $(OBJDIR)/shun_.c \
398 $(OBJDIR)/sitemap_.c \
399 $(OBJDIR)/skins_.c \
400 $(OBJDIR)/smtp_.c \
401 $(OBJDIR)/sqlcmd_.c \
402 $(OBJDIR)/sqldiff_.c \
403 $(OBJDIR)/stash_.c \
404 $(OBJDIR)/stat_.c \
405 $(OBJDIR)/statrep_.c \
406 $(OBJDIR)/style_.c \
407 $(OBJDIR)/sync_.c \
@@ -545,10 +547,11 @@
547 $(OBJDIR)/shun.o \
548 $(OBJDIR)/sitemap.o \
549 $(OBJDIR)/skins.o \
550 $(OBJDIR)/smtp.o \
551 $(OBJDIR)/sqlcmd.o \
552 $(OBJDIR)/sqldiff.o \
553 $(OBJDIR)/stash.o \
554 $(OBJDIR)/stat.o \
555 $(OBJDIR)/statrep.o \
556 $(OBJDIR)/style.o \
557 $(OBJDIR)/sync.o \
@@ -880,10 +883,11 @@
883 $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
884 $(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
885 $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
886 $(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \
887 $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h \
888 $(OBJDIR)/sqldiff_.c:$(OBJDIR)/sqldiff.h \
889 $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \
890 $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \
891 $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \
892 $(OBJDIR)/style_.c:$(OBJDIR)/style.h \
893 $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \
@@ -1860,10 +1864,18 @@
1864
1865 $(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h
1866 $(XTCC) -o $(OBJDIR)/sqlcmd.o -c $(OBJDIR)/sqlcmd_.c
1867
1868 $(OBJDIR)/sqlcmd.h: $(OBJDIR)/headers
1869
1870 $(OBJDIR)/sqldiff_.c: $(SRCDIR)/sqldiff.c $(OBJDIR)/translate
1871 $(OBJDIR)/translate $(SRCDIR)/sqldiff.c >$@
1872
1873 $(OBJDIR)/sqldiff.o: $(OBJDIR)/sqldiff_.c $(OBJDIR)/sqldiff.h $(SRCDIR)/config.h
1874 $(XTCC) -o $(OBJDIR)/sqldiff.o -c $(OBJDIR)/sqldiff_.c
1875
1876 $(OBJDIR)/sqldiff.h: $(OBJDIR)/headers
1877
1878 $(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(OBJDIR)/translate
1879 $(OBJDIR)/translate $(SRCDIR)/stash.c >$@
1880
1881 $(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h
1882
1883 DDED src/sqldiff.c
+1006
--- a/src/sqldiff.c
+++ b/src/sqldiff.c
@@ -0,0 +1,1006 @@
1
+/*
2
+** Copyright (c) 2024 D. Richard Hipp
3
+**
4
+** This program is free software; you can redistribute it and/or
5
+** modify it under the terms of the Simplified BSD License (also
6
+** known as the "2-Clause License" or "FreeBSD License".)
7
+**
8
+** This program is distributed in the hope that it will be useful,
9
+** but without any warranty; without even the implied warranty of
10
+** merchantability or fitness for a particular purpose.
11
+**
12
+** Author contact information:
13
+** [email protected]
14
+** http://www.hwaci.com/drh/
15
+**
16
+*******************************************************************************
17
+**
18
+** This file contains code used to compute a "diff" between two SQLite
19
+** database files for display by Fossil.
20
+**
21
+** Fossil normally only computes diffs on text files. But I was inspired
22
+** by a Hacker News post to add support for diffs of other kinds of files
23
+** as well. The HN post in question is:
24
+**
25
+** https://news.ycombinator.com/item?id=42141370
26
+**
27
+** eternityforest | on: On Building Git for Lawyers
28
+** I really think Git should just add builtin support for binaries,
29
+** and diffing for SQLite and .zip. it's not like it would be all
30
+** that much code....
31
+**
32
+** This file borrows a lot of code from the "sqldiff.c" module of
33
+** SQLite. (https://sqlite.org/src/file/tool/sqldiff.c)
34
+*/
35
+#include "config.h"
36
+#include "sqldiff.h"
37
+#include <ctype.h>
38
+
39
+#if INTERFACE
40
+/*
41
+** Context for an SQL diff
42
+*/
43
+struct SqlDiffCtx {
44
+ int bSchemaOnly; /* Only show schema differences */
45
+ int bSchemaPK; /* Use the schema-defined PK, not the true PK */
46
+ int bHandleVtab; /* Handle fts3, fts4, fts5 and rtree vtabs */
47
+ unsigned fDebug; /* Debug flags */
48
+ int bSchemaCompare; /* Doing single-table sqlite_schema compare */
49
+ int nErr; /* Number of errors encountered */
50
+ Blob *out; /* Write the diff output here */
51
+ sqlite3 *db; /* The database connection */
52
+};
53
+
54
+/*
55
+** Allowed values for SqlDiffCtx.fDebug
56
+*/
57
+#define SQLDIFF_COLUMN_NAMES 0x000001
58
+#define SQLDIFF_DIFF_SQL 0x000002
59
+#define SQLDIFF_SHOW_ERRORS 0x000004
60
+
61
+#endif /* INTERFACE */
62
+
63
+
64
+/*
65
+** Return true if the input Blob superficially resembles an SQLite
66
+** database file.
67
+*/
68
+static int looks_like_sqlite_db(const Blob *pDb){
69
+ int sz = blob_size(pDb);
70
+ const u8 *a = (const u8*)blob_buffer(pDb);
71
+ static const u8 aSqliteHeader[16] = {
72
+ 0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66,
73
+ 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00
74
+ };
75
+
76
+ if( sz<512 ) return 0;
77
+ if( (sz%512)!=0 ) return 0;
78
+ if( memcmp(aSqliteHeader,a,16)!=0 ) return 0;
79
+ return 1;
80
+}
81
+
82
+/*
83
+** Clear and free an sqlite3_str object
84
+*/
85
+static void strFree(sqlite3_str *pStr){
86
+ sqlite3_free(sqlite3_str_finish(pStr));
87
+}
88
+
89
+/*
90
+** Print an error message for an error that occurs at runtime.
91
+*/
92
+static void sqldiffError(SqlDiffCtx *p, const char *zFormat, ...){
93
+ if( p->fDebug & SQLDIFF_SHOW_ERRORS ){
94
+ sqlite3_str *pOut = sqlite3_str_new(0);
95
+ va_list ap;
96
+ va_start(ap, zFormat);
97
+ sqlite3_str_vappendf(pOut, zFormat, ap);
98
+ va_end(ap);
99
+ fossil_print("%s\n", sqlite3_str_value(pOut));
100
+ strFree(pOut);
101
+ }
102
+ p->nErr++;
103
+}
104
+
105
+/* Safely quote an SQL identifier. Use the minimum amount of transformation
106
+** necessary to allow the string to be used with %s.
107
+**
108
+** Space to hold the returned string is obtained from sqlite3_malloc(). The
109
+** caller is responsible for ensuring this space is freed when no longer
110
+** needed.
111
+*/
112
+static char *safeId(const char *zId){
113
+ int i, x;
114
+ char c;
115
+ if( zId[0]==0 ) return sqlite3_mprintf("\"\"");
116
+ for(i=x=0; (c = zId[i])!=0; i++){
117
+ if( !isalpha(c) && c!='_' ){
118
+ if( i>0 && isdigit(c) ){
119
+ x++;
120
+ }else{
121
+ return sqlite3_mprintf("\"%w\"", zId);
122
+ }
123
+ }
124
+ }
125
+ if( x || !sqlite3_keyword_check(zId,i) ){
126
+ return sqlite3_mprintf("%s", zId);
127
+ }
128
+ return sqlite3_mprintf("\"%w\"", zId);
129
+}
130
+
131
+/*
132
+** Prepare a new SQL statement. Print an error and abort if anything
133
+** goes wrong.
134
+*/
135
+static sqlite3_stmt *sqldiff_vprepare(
136
+ SqlDiffCtx *p,
137
+ const char *zFormat,
138
+ va_list ap
139
+){
140
+ char *zSql;
141
+ int rc;
142
+ sqlite3_stmt *pStmt;
143
+
144
+ zSql = sqlite3_vmprintf(zFormat, ap);
145
+ if( zSql==0 ) fossil_fatal("out of memory\n");
146
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
147
+ if( rc ){
148
+ sqldiffError(p, "SQL statement error: %s\n\"%s\"", sqlite3_errmsg(p->db),
149
+ zSql);
150
+ sqlite3_finalize(pStmt);
151
+ pStmt = 0;
152
+ }
153
+ sqlite3_free(zSql);
154
+ return pStmt;
155
+}
156
+static sqlite3_stmt *sqldiff_prepare(SqlDiffCtx *p, const char *zFormat, ...){
157
+ va_list ap;
158
+ sqlite3_stmt *pStmt;
159
+ va_start(ap, zFormat);
160
+ pStmt = sqldiff_vprepare(p, zFormat, ap);
161
+ va_end(ap);
162
+ return pStmt;
163
+}
164
+
165
+/*
166
+** Free a list of strings
167
+*/
168
+static void namelistFree(char **az){
169
+ if( az ){
170
+ int i;
171
+ for(i=0; az[i]; i++) sqlite3_free(az[i]);
172
+ sqlite3_free(az);
173
+ }
174
+}
175
+
176
+/*
177
+** Return a list of column names [a] for the table zDb.zTab. Space to
178
+** hold the list is obtained from sqlite3_malloc() and should released
179
+** using namelistFree() when no longer needed.
180
+**
181
+** Primary key columns are listed first, followed by data columns.
182
+** The number of columns in the primary key is returned in *pnPkey.
183
+**
184
+** Normally [a], the "primary key" in the previous sentence is the true
185
+** primary key - the rowid or INTEGER PRIMARY KEY for ordinary tables
186
+** or the declared PRIMARY KEY for WITHOUT ROWID tables. However, if
187
+** the p->bSchemaPK flag is set, then the schema-defined PRIMARY KEY is
188
+** used in all cases. In that case, entries that have NULL values in
189
+** any of their primary key fields will be excluded from the analysis.
190
+**
191
+** If the primary key for a table is the rowid but rowid is inaccessible,
192
+** then this routine returns a NULL pointer.
193
+**
194
+** [a. If the lone, named table is "sqlite_schema", "rootpage" column is
195
+** omitted and the "type" and "name" columns are made to be the PK.]
196
+**
197
+** Examples:
198
+** CREATE TABLE t1(a INT UNIQUE, b INTEGER, c TEXT, PRIMARY KEY(c));
199
+** *pnPKey = 1;
200
+** az = { "rowid", "a", "b", "c", 0 } // Normal case
201
+** az = { "c", "a", "b", 0 } // g.bSchemaPK==1
202
+**
203
+** CREATE TABLE t2(a INT UNIQUE, b INTEGER, c TEXT, PRIMARY KEY(b));
204
+** *pnPKey = 1;
205
+** az = { "b", "a", "c", 0 }
206
+**
207
+** CREATE TABLE t3(x,y,z,PRIMARY KEY(y,z));
208
+** *pnPKey = 1 // Normal case
209
+** az = { "rowid", "x", "y", "z", 0 } // Normal case
210
+** *pnPKey = 2 // g.bSchemaPK==1
211
+** az = { "y", "x", "z", 0 } // g.bSchemaPK==1
212
+**
213
+** CREATE TABLE t4(x,y,z,PRIMARY KEY(y,z)) WITHOUT ROWID;
214
+** *pnPKey = 2
215
+** az = { "y", "z", "x", 0 }
216
+**
217
+** CREATE TABLE t5(rowid,_rowid_,oid);
218
+** az = 0 // The rowid is not accessible
219
+*/
220
+static char **columnNames(
221
+ SqlDiffCtx *p, /* Diffing context */
222
+ const char *zDb, /* Database ("aaa" or "bbb") to query */
223
+ const char *zTab, /* Name of table to return details of */
224
+ int *pnPKey, /* OUT: Number of PK columns */
225
+ int *pbRowid /* OUT: True if PK is an implicit rowid */
226
+){
227
+ char **az = 0; /* List of column names to be returned */
228
+ int naz = 0; /* Number of entries in az[] */
229
+ sqlite3_stmt *pStmt; /* SQL statement being run */
230
+ char *zPkIdxName = 0; /* Name of the PRIMARY KEY index */
231
+ int truePk = 0; /* PRAGMA table_info indentifies the PK to use */
232
+ int nPK = 0; /* Number of PRIMARY KEY columns */
233
+ int i, j; /* Loop counters */
234
+
235
+ if( p->bSchemaPK==0 ){
236
+ /* Normal case: Figure out what the true primary key is for the table.
237
+ ** * For WITHOUT ROWID tables, the true primary key is the same as
238
+ ** the schema PRIMARY KEY, which is guaranteed to be present.
239
+ ** * For rowid tables with an INTEGER PRIMARY KEY, the true primary
240
+ ** key is the INTEGER PRIMARY KEY.
241
+ ** * For all other rowid tables, the rowid is the true primary key.
242
+ */
243
+ pStmt = sqldiff_prepare(p, "PRAGMA %s.index_list=%Q", zDb, zTab);
244
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
245
+ if( sqlite3_stricmp((const char*)sqlite3_column_text(pStmt,3),"pk")==0 ){
246
+ zPkIdxName = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1));
247
+ break;
248
+ }
249
+ }
250
+ sqlite3_finalize(pStmt);
251
+ if( zPkIdxName ){
252
+ int nKey = 0;
253
+ int nCol = 0;
254
+ truePk = 0;
255
+ pStmt = sqldiff_prepare(p, "PRAGMA %s.index_xinfo=%Q", zDb, zPkIdxName);
256
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
257
+ nCol++;
258
+ if( sqlite3_column_int(pStmt,5) ){ nKey++; continue; }
259
+ if( sqlite3_column_int(pStmt,1)>=0 ) truePk = 1;
260
+ }
261
+ if( nCol==nKey ) truePk = 1;
262
+ if( truePk ){
263
+ nPK = nKey;
264
+ }else{
265
+ nPK = 1;
266
+ }
267
+ sqlite3_finalize(pStmt);
268
+ sqlite3_free(zPkIdxName);
269
+ }else{
270
+ truePk = 1;
271
+ nPK = 1;
272
+ }
273
+ pStmt = sqldiff_prepare(p, "PRAGMA %s.table_info=%Q", zDb, zTab);
274
+ }else{
275
+ /* The p->bSchemaPK==1 case: Use whatever primary key is declared
276
+ ** in the schema. The "rowid" will still be used as the primary key
277
+ ** if the table definition does not contain a PRIMARY KEY.
278
+ */
279
+ nPK = 0;
280
+ pStmt = sqldiff_prepare(p, "PRAGMA %s.table_info=%Q", zDb, zTab);
281
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
282
+ if( sqlite3_column_int(pStmt,5)>0 ) nPK++;
283
+ }
284
+ sqlite3_reset(pStmt);
285
+ if( nPK==0 ) nPK = 1;
286
+ truePk = 1;
287
+ }
288
+ if( p->bSchemaCompare ){
289
+ assert( sqlite3_stricmp(zTab,"sqlite_schema")==0
290
+ || sqlite3_stricmp(zTab,"sqlite_master")==0 );
291
+ /* For sqlite_schema, will use type and name as the PK. */
292
+ nPK = 2;
293
+ truePk = 0;
294
+ }
295
+ *pnPKey = nPK;
296
+ naz = nPK;
297
+ az = sqlite3_malloc( sizeof(char*)*(nPK+1) );
298
+ if( az==0 ) fossil_fatal("out of memory\n");
299
+ memset(az, 0, sizeof(char*)*(nPK+1));
300
+ if( p->bSchemaCompare ){
301
+ az[0] = sqlite3_mprintf("%s", "type");
302
+ az[1] = sqlite3_mprintf("%s", "name");
303
+ }
304
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
305
+ char * sid = safeId((char*)sqlite3_column_text(pStmt,1));
306
+ int iPKey;
307
+ if( truePk && (iPKey = sqlite3_column_int(pStmt,5))>0 ){
308
+ az[iPKey-1] = sid;
309
+ }else{
310
+ if( !p->bSchemaCompare
311
+ || !(strcmp(sid,"rootpage")==0
312
+ ||strcmp(sid,"name")==0
313
+ ||strcmp(sid,"type")==0)){
314
+ az = sqlite3_realloc(az, sizeof(char*)*(naz+2) );
315
+ if( az==0 ) fossil_fatal("out of memory\n");
316
+ az[naz++] = sid;
317
+ }
318
+ }
319
+ }
320
+ sqlite3_finalize(pStmt);
321
+ if( az ) az[naz] = 0;
322
+
323
+ /* If it is non-NULL, set *pbRowid to indicate whether or not the PK of
324
+ ** this table is an implicit rowid (*pbRowid==1) or not (*pbRowid==0). */
325
+ if( pbRowid ) *pbRowid = (az[0]==0);
326
+
327
+ /* If this table has an implicit rowid for a PK, figure out how to refer
328
+ ** to it. There are usually three options - "rowid", "_rowid_" and "oid".
329
+ ** Any of these will work, unless the table has an explicit column of the
330
+ ** same name or the sqlite_schema tables are to be compared. In the latter
331
+ ** case, pretend that the "true" primary key is the name column, which
332
+ ** avoids extraneous diffs against the schemas due to rowid variance. */
333
+ if( az[0]==0 ){
334
+ const char *azRowid[] = { "rowid", "_rowid_", "oid" };
335
+ for(i=0; i<sizeof(azRowid)/sizeof(azRowid[0]); i++){
336
+ for(j=1; j<naz; j++){
337
+ if( sqlite3_stricmp(az[j], azRowid[i])==0 ) break;
338
+ }
339
+ if( j>=naz ){
340
+ az[0] = sqlite3_mprintf("%s", azRowid[i]);
341
+ break;
342
+ }
343
+ }
344
+ if( az[0]==0 ){
345
+ for(i=1; i<naz; i++) sqlite3_free(az[i]);
346
+ sqlite3_free(az);
347
+ az = 0;
348
+ }
349
+ }
350
+ return az;
351
+}
352
+
353
+/*
354
+** Print the sqlite3_value X as an SQL literal.
355
+*/
356
+static void printQuoted(Blob *out, sqlite3_value *X){
357
+ switch( sqlite3_value_type(X) ){
358
+ case SQLITE_FLOAT: {
359
+ double r1;
360
+ char zBuf[50];
361
+ r1 = sqlite3_value_double(X);
362
+ sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
363
+ blob_appendf(out, "%s", zBuf);
364
+ break;
365
+ }
366
+ case SQLITE_INTEGER: {
367
+ blob_appendf(out, "%lld", sqlite3_value_int64(X));
368
+ break;
369
+ }
370
+ case SQLITE_BLOB: {
371
+ const unsigned char *zBlob = sqlite3_value_blob(X);
372
+ int nBlob = sqlite3_value_bytes(X);
373
+ if( zBlob ){
374
+ int i;
375
+ blob_appendf(out, "x'");
376
+ for(i=0; i<nBlob; i++){
377
+ blob_appendf(out, "%02x", zBlob[i]);
378
+ }
379
+ blob_appendf(out, "'");
380
+ }else{
381
+ /* Could be an OOM, could be a zero-byte blob */
382
+ blob_appendf(out, "X''");
383
+ }
384
+ break;
385
+ }
386
+ case SQLITE_TEXT: {
387
+ const unsigned char *zArg = sqlite3_value_text(X);
388
+
389
+ if( zArg==0 ){
390
+ blob_appendf(out, "NULL");
391
+ }else{
392
+ int inctl = 0;
393
+ int i, j;
394
+ blob_appendf(out, "'");
395
+ for(i=j=0; zArg[i]; i++){
396
+ char c = zArg[i];
397
+ int ctl = iscntrl((unsigned char)c);
398
+ if( ctl>inctl ){
399
+ inctl = ctl;
400
+ blob_appendf(out, "%.*s'||X'%02x", i-j, &zArg[j], c);
401
+ j = i+1;
402
+ }else if( ctl ){
403
+ blob_appendf(out, "%02x", c);
404
+ j = i+1;
405
+ }else{
406
+ if( inctl ){
407
+ inctl = 0;
408
+ blob_appendf(out, "'\n||'");
409
+ }
410
+ if( c=='\'' ){
411
+ blob_appendf(out, "%.*s'", i-j+1, &zArg[j]);
412
+ j = i+1;
413
+ }
414
+ }
415
+ }
416
+ blob_appendf(out, "%s'", &zArg[j]);
417
+ }
418
+ break;
419
+ }
420
+ case SQLITE_NULL: {
421
+ blob_appendf(out, "NULL");
422
+ break;
423
+ }
424
+ }
425
+}
426
+
427
+/*
428
+** Output SQL that will recreate the bbb.zTab table.
429
+*/
430
+static void dump_table(SqlDiffCtx *p, const char *zTab){
431
+ char *zId = safeId(zTab); /* Name of the table */
432
+ char **az = 0; /* List of columns */
433
+ int nPk; /* Number of true primary key columns */
434
+ int nCol; /* Number of data columns */
435
+ int i; /* Loop counter */
436
+ sqlite3_stmt *pStmt; /* SQL statement */
437
+ const char *zSep; /* Separator string */
438
+ sqlite3_str *pIns; /* Beginning of the INSERT statement */
439
+
440
+ pStmt = sqldiff_prepare(p,
441
+ "SELECT sql FROM bbb.sqlite_schema WHERE name=%Q", zTab);
442
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
443
+ blob_appendf(p->out, "%s;\n", sqlite3_column_text(pStmt,0));
444
+ }
445
+ sqlite3_finalize(pStmt);
446
+ if( !p->bSchemaOnly ){
447
+ az = columnNames(p, "bbb", zTab, &nPk, 0);
448
+ pIns = sqlite3_str_new(0);
449
+ if( az==0 ){
450
+ pStmt = sqldiff_prepare(p, "SELECT * FROM bbb.%s", zId);
451
+ sqlite3_str_appendf(pIns,"INSERT INTO %s VALUES", zId);
452
+ }else{
453
+ sqlite3_str *pSql = sqlite3_str_new(0);
454
+ zSep = "SELECT";
455
+ for(i=0; az[i]; i++){
456
+ sqlite3_str_appendf(pSql, "%s %s", zSep, az[i]);
457
+ zSep = ",";
458
+ }
459
+ sqlite3_str_appendf(pSql," FROM bbb.%s", zId);
460
+ zSep = " ORDER BY";
461
+ for(i=1; i<=nPk; i++){
462
+ sqlite3_str_appendf(pSql, "%s %d", zSep, i);
463
+ zSep = ",";
464
+ }
465
+ pStmt = sqldiff_prepare(p, "%s", sqlite3_str_value(pSql));
466
+ strFree(pSql);
467
+ sqlite3_str_appendf(pIns, "INSERT INTO %s", zId);
468
+ zSep = "(";
469
+ for(i=0; az[i]; i++){
470
+ sqlite3_str_appendf(pIns, "%s%s", zSep, az[i]);
471
+ zSep = ",";
472
+ }
473
+ sqlite3_str_appendf(pIns,") VALUES");
474
+ namelistFree(az);
475
+ }
476
+ nCol = sqlite3_column_count(pStmt);
477
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
478
+ blob_appendf(p->out, "%s",sqlite3_str_value(pIns));
479
+ zSep = "(";
480
+ for(i=0; i<nCol; i++){
481
+ blob_appendf(p->out, "%s",zSep);
482
+ printQuoted(p->out, sqlite3_column_value(pStmt,i));
483
+ zSep = ",";
484
+ }
485
+ blob_appendf(p->out, ");\n");
486
+ }
487
+ sqlite3_finalize(pStmt);
488
+ strFree(pIns);
489
+ } /* endif !p->bSchemaOnly */
490
+ pStmt = sqldiff_prepare(p, "SELECT sql FROM bbb.sqlite_schema"
491
+ " WHERE type='index' AND tbl_name=%Q AND sql IS NOT NULL",
492
+ zTab);
493
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
494
+ blob_appendf(p->out, "%s;\n", sqlite3_column_text(pStmt,0));
495
+ }
496
+ sqlite3_finalize(pStmt);
497
+ sqlite3_free(zId);
498
+}
499
+
500
+
501
+/*
502
+** Compute all differences for a single table, except if the
503
+** table name is sqlite_schema, ignore the rootpage column.
504
+*/
505
+static void diff_one_table(SqlDiffCtx *p, const char *zTab){
506
+ char *zId = safeId(zTab); /* Name of table (translated for us in SQL) */
507
+ char **az = 0; /* Columns in aaa */
508
+ char **az2 = 0; /* Columns in bbb */
509
+ int nPk; /* Primary key columns in aaa */
510
+ int nPk2; /* Primary key columns in bbb */
511
+ int n = 0; /* Number of columns in aaa */
512
+ int n2; /* Number of columns in bbb */
513
+ int nQ; /* Number of output columns in the diff query */
514
+ int i; /* Loop counter */
515
+ const char *zSep; /* Separator string */
516
+ sqlite3_str *pSql; /* Comparison query */
517
+ sqlite3_stmt *pStmt; /* Query statement to do the diff */
518
+ const char *zLead = /* Becomes line-comment for sqlite_schema */
519
+ (p->bSchemaCompare)? "-- " : "";
520
+
521
+ pSql = sqlite3_str_new(0);
522
+ if( p->fDebug==SQLDIFF_COLUMN_NAMES ){
523
+ /* Simply run columnNames() on all tables of the origin
524
+ ** database and show the results. This is used for testing
525
+ ** and debugging of the columnNames() function.
526
+ */
527
+ az = columnNames(p, "bbb",zTab, &nPk, 0);
528
+ if( az==0 ){
529
+ fossil_print("Rowid not accessible for %s\n", zId);
530
+ }else{
531
+ fossil_print("%s:", zId);
532
+ for(i=0; az[i]; i++){
533
+ fossil_print(" %s", az[i]);
534
+ if( i+1==nPk ) fossil_print(" *");
535
+ }
536
+ fossil_print("\n");
537
+ }
538
+ goto end_diff_one_table;
539
+ }
540
+
541
+ if( sqlite3_table_column_metadata(p->db,"bbb",zTab,0,0,0,0,0,0) ){
542
+ if( !sqlite3_table_column_metadata(p->db,"aaa",zTab,0,0,0,0,0,0) ){
543
+ /* Table missing from second database. */
544
+ if( p->bSchemaCompare ){
545
+ blob_appendf(p->out, "-- 2nd DB has no %s table\n", zTab);
546
+ }else{
547
+ blob_appendf(p->out, "DROP TABLE %s;\n", zId);
548
+ }
549
+ }
550
+ goto end_diff_one_table;
551
+ }
552
+
553
+ if( sqlite3_table_column_metadata(p->db,"aaa",zTab,0,0,0,0,0,0) ){
554
+ /* Table missing from source */
555
+ if( p->bSchemaCompare ){
556
+ blob_appendf(p->out, "-- 1st DB has no %s table\n", zTab);
557
+ }else{
558
+ dump_table(p, zTab);
559
+ }
560
+ goto end_diff_one_table;
561
+ }
562
+
563
+ az = columnNames(p, "aaa", zTab, &nPk, 0);
564
+ az2 = columnNames(p, "bbb", zTab, &nPk2, 0);
565
+ if( az && az2 ){
566
+ for(n=0; az[n] && az2[n]; n++){
567
+ if( sqlite3_stricmp(az[n],az2[n])!=0 ) break;
568
+ }
569
+ }
570
+ if( az==0
571
+ || az2==0
572
+ || nPk!=nPk2
573
+ || az[n]
574
+ ){
575
+ /* Schema mismatch */
576
+ blob_appendf(p->out, "%sDROP TABLE %s; -- due to schema mismatch\n",
577
+ zLead, zId);
578
+ dump_table(p, zTab);
579
+ goto end_diff_one_table;
580
+ }
581
+
582
+ /* Build the comparison query */
583
+ for(n2=n; az2[n2]; n2++){
584
+ char *zNTab = safeId(az2[n2]);
585
+ blob_appendf(p->out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zNTab);
586
+ sqlite3_free(zNTab);
587
+ }
588
+ nQ = nPk2+1+2*(n2-nPk2);
589
+ if( n2>nPk2 ){
590
+ zSep = "SELECT ";
591
+ for(i=0; i<nPk; i++){
592
+ sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
593
+ zSep = ", ";
594
+ }
595
+ sqlite3_str_appendf(pSql, ", 1 /* changed row */");
596
+ while( az[i] ){
597
+ sqlite3_str_appendf(pSql, ", A.%s IS NOT B.%s, B.%s",
598
+ az[i], az2[i], az2[i]);
599
+ i++;
600
+ }
601
+ while( az2[i] ){
602
+ sqlite3_str_appendf(pSql, ", B.%s IS NOT NULL, B.%s",
603
+ az2[i], az2[i]);
604
+ i++;
605
+ }
606
+ sqlite3_str_appendf(pSql, "\n FROM aaa.%s A, bbb.%s B\n", zId, zId);
607
+ zSep = " WHERE";
608
+ for(i=0; i<nPk; i++){
609
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
610
+ zSep = " AND";
611
+ }
612
+ zSep = "\n AND (";
613
+ while( az[i] ){
614
+ sqlite3_str_appendf(pSql, "%sA.%s IS NOT B.%s%s\n",
615
+ zSep, az[i], az2[i], az2[i+1]==0 ? ")" : "");
616
+ zSep = " OR ";
617
+ i++;
618
+ }
619
+ while( az2[i] ){
620
+ sqlite3_str_appendf(pSql, "%sB.%s IS NOT NULL%s\n",
621
+ zSep, az2[i], az2[i+1]==0 ? ")" : "");
622
+ zSep = " OR ";
623
+ i++;
624
+ }
625
+ sqlite3_str_appendf(pSql, " UNION ALL\n");
626
+ }
627
+ zSep = "SELECT ";
628
+ for(i=0; i<nPk; i++){
629
+ sqlite3_str_appendf(pSql, "%sA.%s", zSep, az[i]);
630
+ zSep = ", ";
631
+ }
632
+ sqlite3_str_appendf(pSql, ", 2 /* deleted row */");
633
+ while( az2[i] ){
634
+ sqlite3_str_appendf(pSql, ", NULL, NULL");
635
+ i++;
636
+ }
637
+ sqlite3_str_appendf(pSql, "\n FROM aaa.%s A\n", zId);
638
+ sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM bbb.%s B\n", zId);
639
+ zSep = " WHERE";
640
+ for(i=0; i<nPk; i++){
641
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
642
+ zSep = " AND";
643
+ }
644
+ sqlite3_str_appendf(pSql, ")\n");
645
+ zSep = " UNION ALL\nSELECT ";
646
+ for(i=0; i<nPk; i++){
647
+ sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
648
+ zSep = ", ";
649
+ }
650
+ sqlite3_str_appendf(pSql, ", 3 /* inserted row */");
651
+ while( az2[i] ){
652
+ sqlite3_str_appendf(pSql, ", 1, B.%s", az2[i]);
653
+ i++;
654
+ }
655
+ sqlite3_str_appendf(pSql, "\n FROM bbb.%s B\n", zId);
656
+ sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM aaa.%s A\n", zId);
657
+ zSep = " WHERE";
658
+ for(i=0; i<nPk; i++){
659
+ sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
660
+ zSep = " AND";
661
+ }
662
+ sqlite3_str_appendf(pSql, ")\n ORDER BY");
663
+ zSep = " ";
664
+ for(i=1; i<=nPk; i++){
665
+ sqlite3_str_appendf(pSql, "%s%d", zSep, i);
666
+ zSep = ", ";
667
+ }
668
+ sqlite3_str_appendf(pSql, ";\n");
669
+
670
+ if( p->fDebug & SQLDIFF_DIFF_SQL ){
671
+ fossil_print("SQL for %s:\n%s\n", zId, sqlite3_str_value(pSql));
672
+ goto end_diff_one_table;
673
+ }
674
+
675
+ /* Drop indexes that are missing in the destination */
676
+ pStmt = sqldiff_prepare(p,
677
+ "SELECT name FROM aaa.sqlite_schema"
678
+ " WHERE type='index' AND tbl_name=%Q"
679
+ " AND sql IS NOT NULL"
680
+ " AND sql NOT IN (SELECT sql FROM bbb.sqlite_schema"
681
+ " WHERE type='index' AND tbl_name=%Q"
682
+ " AND sql IS NOT NULL)",
683
+ zTab, zTab);
684
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
685
+ char *z = safeId((const char*)sqlite3_column_text(pStmt,0));
686
+ blob_appendf(p->out, "DROP INDEX %s;\n", z);
687
+ sqlite3_free(z);
688
+ }
689
+ sqlite3_finalize(pStmt);
690
+
691
+ /* Run the query and output differences */
692
+ if( !p->bSchemaOnly ){
693
+ pStmt = sqldiff_prepare(p, "%s", sqlite3_str_value(pSql));
694
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
695
+ int iType = sqlite3_column_int(pStmt, nPk);
696
+ if( iType==1 || iType==2 ){
697
+ if( iType==1 ){ /* Change the content of a row */
698
+ blob_appendf(p->out, "%sUPDATE %s", zLead, zId);
699
+ zSep = " SET";
700
+ for(i=nPk+1; i<nQ; i+=2){
701
+ if( sqlite3_column_int(pStmt,i)==0 ) continue;
702
+ blob_appendf(p->out, "%s %s=", zSep, az2[(i+nPk-1)/2]);
703
+ zSep = ",";
704
+ printQuoted(p->out, sqlite3_column_value(pStmt,i+1));
705
+ }
706
+ }else{ /* Delete a row */
707
+ blob_appendf(p->out, "%sDELETE FROM %s", zLead, zId);
708
+ }
709
+ zSep = " WHERE";
710
+ for(i=0; i<nPk; i++){
711
+ blob_appendf(p->out, "%s %s=", zSep, az2[i]);
712
+ printQuoted(p->out, sqlite3_column_value(pStmt,i));
713
+ zSep = " AND";
714
+ }
715
+ blob_appendf(p->out, ";\n");
716
+ }else{ /* Insert a row */
717
+ blob_appendf(p->out, "%sINSERT INTO %s(%s", zLead, zId, az2[0]);
718
+ for(i=1; az2[i]; i++) blob_appendf(p->out, ",%s", az2[i]);
719
+ blob_appendf(p->out, ") VALUES");
720
+ zSep = "(";
721
+ for(i=0; i<nPk2; i++){
722
+ blob_appendf(p->out, "%s", zSep);
723
+ zSep = ",";
724
+ printQuoted(p->out, sqlite3_column_value(pStmt,i));
725
+ }
726
+ for(i=nPk2+2; i<nQ; i+=2){
727
+ blob_appendf(p->out, ",");
728
+ printQuoted(p->out, sqlite3_column_value(pStmt,i));
729
+ }
730
+ blob_appendf(p->out, ");\n");
731
+ }
732
+ }
733
+ sqlite3_finalize(pStmt);
734
+ } /* endif !p->bSchemaOnly */
735
+
736
+ /* Create indexes that are missing in the source */
737
+ pStmt = sqldiff_prepare(p,
738
+ "SELECT sql FROM bbb.sqlite_schema"
739
+ " WHERE type='index' AND tbl_name=%Q"
740
+ " AND sql IS NOT NULL"
741
+ " AND sql NOT IN (SELECT sql FROM aaa.sqlite_schema"
742
+ " WHERE type='index' AND tbl_name=%Q"
743
+ " AND sql IS NOT NULL)",
744
+ zTab, zTab);
745
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
746
+ blob_appendf(p->out, "%s;\n", sqlite3_column_text(pStmt,0));
747
+ }
748
+ sqlite3_finalize(pStmt);
749
+
750
+end_diff_one_table:
751
+ strFree(pSql);
752
+ sqlite3_free(zId);
753
+ namelistFree(az);
754
+ namelistFree(az2);
755
+ return;
756
+}
757
+
758
+#if 0
759
+/*
760
+** Check that table zTab exists and has the same schema in both the "aaa"
761
+** and "bbb" databases currently opened by the global db handle. If they
762
+** do not, output an error message on stderr and exit(1). Otherwise, if
763
+** the schemas do match, return control to the caller.
764
+*/
765
+static void checkSchemasMatch(SqlDiffCtx *p, const char *zTab){
766
+ sqlite3_stmt *pStmt = sqldiff_prepare(p,
767
+ "SELECT A.sql=B.sql FROM aaa.sqlite_schema A, bbb.sqlite_schema B"
768
+ " WHERE A.name=%Q AND B.name=%Q", zTab, zTab
769
+ );
770
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
771
+ if( sqlite3_column_int(pStmt,0)==0 ){
772
+ sqldiffError(p, "schema changes for table %s", safeId(zTab));
773
+ }
774
+ }else{
775
+ sqldiffError(p, "table %s missing from one or both databases",safeId(zTab));
776
+ }
777
+ sqlite3_finalize(pStmt);
778
+}
779
+#endif
780
+
781
+/*
782
+** Return true if the ascii character passed as the only argument is a
783
+** whitespace character. Otherwise return false.
784
+*/
785
+static int is_whitespace(char x){
786
+ return (x==' ' || x=='\t' || x=='\n' || x=='\r');
787
+}
788
+
789
+/*
790
+** Extract the next SQL keyword or quoted string from buffer zIn and copy it
791
+** (or a prefix of it if it will not fit) into buffer zBuf, size nBuf bytes.
792
+** Return a pointer to the character within zIn immediately following
793
+** the token or quoted string just extracted.
794
+*/
795
+static const char *gobble_token(const char *zIn, char *zBuf, int nBuf){
796
+ const char *p = zIn;
797
+ char *pOut = zBuf;
798
+ char *pEnd = &pOut[nBuf-1];
799
+ char q = 0; /* quote character, if any */
800
+
801
+ if( p==0 ) return 0;
802
+ while( is_whitespace(*p) ) p++;
803
+ switch( *p ){
804
+ case '"': q = '"'; break;
805
+ case '\'': q = '\''; break;
806
+ case '`': q = '`'; break;
807
+ case '[': q = ']'; break;
808
+ }
809
+
810
+ if( q ){
811
+ p++;
812
+ while( *p && pOut<pEnd ){
813
+ if( *p==q ){
814
+ p++;
815
+ if( *p!=q ) break;
816
+ }
817
+ if( pOut<pEnd ) *pOut++ = *p;
818
+ p++;
819
+ }
820
+ }else{
821
+ while( *p && !is_whitespace(*p) && *p!='(' ){
822
+ if( pOut<pEnd ) *pOut++ = *p;
823
+ p++;
824
+ }
825
+ }
826
+
827
+ *pOut = '\0';
828
+ return p;
829
+}
830
+
831
+/*
832
+** This function is the implementation of SQL scalar function "module_name":
833
+**
834
+** module_name(SQL)
835
+**
836
+** The only argument should be an SQL statement of the type that may appear
837
+** in the sqlite_schema table. If the statement is a "CREATE VIRTUAL TABLE"
838
+** statement, then the value returned is the name of the module that it
839
+** uses. Otherwise, if the statement is not a CVT, NULL is returned.
840
+*/
841
+static void module_name_func(
842
+ sqlite3_context *pCtx,
843
+ int nVal, sqlite3_value **apVal
844
+){
845
+ const char *zSql;
846
+ char zToken[32];
847
+
848
+ assert( nVal==1 );
849
+ zSql = (const char*)sqlite3_value_text(apVal[0]);
850
+
851
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
852
+ if( zSql==0 || sqlite3_stricmp(zToken, "create") ) return;
853
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
854
+ if( zSql==0 || sqlite3_stricmp(zToken, "virtual") ) return;
855
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
856
+ if( zSql==0 || sqlite3_stricmp(zToken, "table") ) return;
857
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
858
+ if( zSql==0 ) return;
859
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
860
+ if( zSql==0 || sqlite3_stricmp(zToken, "using") ) return;
861
+ zSql = gobble_token(zSql, zToken, sizeof(zToken));
862
+
863
+ sqlite3_result_text(pCtx, zToken, -1, SQLITE_TRANSIENT);
864
+}
865
+
866
+/*
867
+** Return the text of an SQL statement that itself returns the list of
868
+** tables to process within the database.
869
+*/
870
+const char *all_tables_sql(SqlDiffCtx *p){
871
+ if( p->bHandleVtab ){
872
+ int rc;
873
+
874
+ rc = sqlite3_exec(p->db,
875
+ "CREATE TEMP TABLE tblmap(module COLLATE nocase, postfix);"
876
+ "INSERT INTO temp.tblmap VALUES"
877
+ "('fts3', '_content'), ('fts3', '_segments'), ('fts3', '_segdir'),"
878
+
879
+ "('fts4', '_content'), ('fts4', '_segments'), ('fts4', '_segdir'),"
880
+ "('fts4', '_docsize'), ('fts4', '_stat'),"
881
+
882
+ "('fts5', '_data'), ('fts5', '_idx'), ('fts5', '_content'),"
883
+ "('fts5', '_docsize'), ('fts5', '_config'),"
884
+
885
+ "('rtree', '_node'), ('rtree', '_rowid'), ('rtree', '_parent');"
886
+ , 0, 0, 0
887
+ );
888
+ assert( rc==SQLITE_OK );
889
+
890
+ rc = sqlite3_create_function(
891
+ p->db, "module_name", 1, SQLITE_UTF8, 0, module_name_func, 0, 0
892
+ );
893
+ assert( rc==SQLITE_OK );
894
+
895
+ return
896
+ "SELECT name FROM aaa.sqlite_schema\n"
897
+ " WHERE type='table' AND (\n"
898
+ " module_name(sql) IS NULL OR \n"
899
+ " module_name(sql) IN (SELECT module FROM temp.tblmap)\n"
900
+ " ) AND name NOT IN (\n"
901
+ " SELECT a.name || b.postfix \n"
902
+ "FROM aaa.sqlite_schema AS a, temp.tblmap AS b \n"
903
+ "WHERE module_name(a.sql) = b.module\n"
904
+ " )\n"
905
+ "UNION \n"
906
+ "SELECT name FROM bbb.sqlite_schema\n"
907
+ " WHERE type='table' AND (\n"
908
+ " module_name(sql) IS NULL OR \n"
909
+ " module_name(sql) IN (SELECT module FROM temp.tblmap)\n"
910
+ " ) AND name NOT IN (\n"
911
+ " SELECT a.name || b.postfix \n"
912
+ "FROM bbb.sqlite_schema AS a, temp.tblmap AS b \n"
913
+ "WHERE module_name(a.sql) = b.module\n"
914
+ " )\n"
915
+ " ORDER BY name";
916
+ }else{
917
+ return
918
+ "SELECT name FROM aaa.sqlite_schema\n"
919
+ " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
920
+ " UNION\n"
921
+ "SELECT name FROM bbb.sqlite_schema\n"
922
+ " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
923
+ " ORDER BY name";
924
+ }
925
+}
926
+
927
+/*
928
+** Check to see if the two input blobs, pA and pB, are both
929
+** SQLite database files. If they are, then output an SQL diff
930
+** into pOut and return true. If either of the inputs is not
931
+** a well-formed SQLite database, then return 0.
932
+**
933
+** A semantic-level diff is computed. In other words, it is the
934
+** content of the database that matters. If the databases have
935
+** different page sizes or text representations or if the pages
936
+** are in a different order, that does not affect the output.
937
+** Only content differences are shown.
938
+*/
939
+int sqldiff(
940
+ Blob *pA, /* FROM file */
941
+ Blob *pB, /* TO file */
942
+ Blob *pOut, /* Write diff here */
943
+ DiffConfig *pCfg /* Configuration options */
944
+){
945
+ SqlDiffCtx s;
946
+ sqlite3_stmt *pStmt;
947
+ int rc;
948
+ u8 *aA, *aB;
949
+ int szA, szB;
950
+ u8 aModeA[2];
951
+ u8 aModeB[2];
952
+
953
+ if( pOut==0 ) return 0;
954
+ if( !looks_like_sqlite_db(pA) ) return 0;
955
+ if( !looks_like_sqlite_db(pB) ) return 0;
956
+ memset(&s, 0, sizeof(s));
957
+ s.out = pOut;
958
+ rc = sqlite3_open(":memory:", &s.db);
959
+ if( rc ){
960
+ fossil_fatal("Unable to open an auxiliary in-memory database\n");
961
+ }
962
+ rc = sqlite3_exec(s.db, "ATTACH ':memory:' AS aaa;", 0, 0, 0);
963
+ if( rc ){
964
+ fossil_fatal("Unable to attach an in-memory database\n");
965
+ }
966
+ rc = sqlite3_exec(s.db, "ATTACH ':memory:' AS bbb;", 0, 0, 0);
967
+ if( rc ){
968
+ fossil_fatal("Unable to attach an in-memory database\n");
969
+ }
970
+ aA = (u8*)blob_buffer(pA);
971
+ szA = blob_size(pA);
972
+ memcpy(aModeA, &aA[18], 2);
973
+ aA[18] = aA[19] = 1;
974
+ aB = (u8*)blob_buffer(pB);
975
+ szB = blob_size(pB);
976
+ memcpy(aModeB, &aB[18], 2);
977
+ aB[18] = aB[19] = 1;
978
+ rc = sqlite3_deserialize(s.db, "aaa", aA, szA, szA,
979
+ SQLITE_DESERIALIZE_READONLY);
980
+ if( rc ){
981
+ s.nErr++;
982
+ goto not_a_valid_diff;
983
+ }
984
+ rc = sqlite3_deserialize(s.db, "bbb", aB, szB, szB,
985
+ SQLITE_DESERIALIZE_READONLY);
986
+ if( rc ){
987
+ s.nErr++;
988
+ goto not_a_valid_diff;
989
+ }
990
+ if( pCfg->diffFlags & DIFF_HTML ) blob_appendf(pOut, "<pre>\n");
991
+ pStmt = sqldiff_prepare(&s, "%s", all_tables_sql(&s) );
992
+ if( pStmt ){
993
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
994
+ diff_one_table(&s, (const char*)sqlite3_column_text(pStmt,0));
995
+ }
996
+ sqlite3_finalize(pStmt);
997
+ }
998
+ if( pCfg->diffFlags & DIFF_HTML ) blob_appendf(pOut, "</pre>\n");
999
+
1000
+not_a_valid_diff:
1001
+ sqlite3_close(s.db);
1002
+ if( s.nErr ) blob_reset(pOut);
1003
+ memcpy(&aA[18], aModeA, 2);
1004
+ memcpy(&aB[18], aModeB, 2);
1005
+ return s.nErr==0;
1006
+}
--- a/src/sqldiff.c
+++ b/src/sqldiff.c
@@ -0,0 +1,1006 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/src/sqldiff.c
+++ b/src/sqldiff.c
@@ -0,0 +1,1006 @@
1 /*
2 ** Copyright (c) 2024 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 **
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 ** [email protected]
14 ** http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains code used to compute a "diff" between two SQLite
19 ** database files for display by Fossil.
20 **
21 ** Fossil normally only computes diffs on text files. But I was inspired
22 ** by a Hacker News post to add support for diffs of other kinds of files
23 ** as well. The HN post in question is:
24 **
25 ** https://news.ycombinator.com/item?id=42141370
26 **
27 ** eternityforest | on: On Building Git for Lawyers
28 ** I really think Git should just add builtin support for binaries,
29 ** and diffing for SQLite and .zip. it's not like it would be all
30 ** that much code....
31 **
32 ** This file borrows a lot of code from the "sqldiff.c" module of
33 ** SQLite. (https://sqlite.org/src/file/tool/sqldiff.c)
34 */
35 #include "config.h"
36 #include "sqldiff.h"
37 #include <ctype.h>
38
39 #if INTERFACE
40 /*
41 ** Context for an SQL diff
42 */
43 struct SqlDiffCtx {
44 int bSchemaOnly; /* Only show schema differences */
45 int bSchemaPK; /* Use the schema-defined PK, not the true PK */
46 int bHandleVtab; /* Handle fts3, fts4, fts5 and rtree vtabs */
47 unsigned fDebug; /* Debug flags */
48 int bSchemaCompare; /* Doing single-table sqlite_schema compare */
49 int nErr; /* Number of errors encountered */
50 Blob *out; /* Write the diff output here */
51 sqlite3 *db; /* The database connection */
52 };
53
54 /*
55 ** Allowed values for SqlDiffCtx.fDebug
56 */
57 #define SQLDIFF_COLUMN_NAMES 0x000001
58 #define SQLDIFF_DIFF_SQL 0x000002
59 #define SQLDIFF_SHOW_ERRORS 0x000004
60
61 #endif /* INTERFACE */
62
63
64 /*
65 ** Return true if the input Blob superficially resembles an SQLite
66 ** database file.
67 */
68 static int looks_like_sqlite_db(const Blob *pDb){
69 int sz = blob_size(pDb);
70 const u8 *a = (const u8*)blob_buffer(pDb);
71 static const u8 aSqliteHeader[16] = {
72 0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66,
73 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00
74 };
75
76 if( sz<512 ) return 0;
77 if( (sz%512)!=0 ) return 0;
78 if( memcmp(aSqliteHeader,a,16)!=0 ) return 0;
79 return 1;
80 }
81
82 /*
83 ** Clear and free an sqlite3_str object
84 */
85 static void strFree(sqlite3_str *pStr){
86 sqlite3_free(sqlite3_str_finish(pStr));
87 }
88
89 /*
90 ** Print an error message for an error that occurs at runtime.
91 */
92 static void sqldiffError(SqlDiffCtx *p, const char *zFormat, ...){
93 if( p->fDebug & SQLDIFF_SHOW_ERRORS ){
94 sqlite3_str *pOut = sqlite3_str_new(0);
95 va_list ap;
96 va_start(ap, zFormat);
97 sqlite3_str_vappendf(pOut, zFormat, ap);
98 va_end(ap);
99 fossil_print("%s\n", sqlite3_str_value(pOut));
100 strFree(pOut);
101 }
102 p->nErr++;
103 }
104
105 /* Safely quote an SQL identifier. Use the minimum amount of transformation
106 ** necessary to allow the string to be used with %s.
107 **
108 ** Space to hold the returned string is obtained from sqlite3_malloc(). The
109 ** caller is responsible for ensuring this space is freed when no longer
110 ** needed.
111 */
112 static char *safeId(const char *zId){
113 int i, x;
114 char c;
115 if( zId[0]==0 ) return sqlite3_mprintf("\"\"");
116 for(i=x=0; (c = zId[i])!=0; i++){
117 if( !isalpha(c) && c!='_' ){
118 if( i>0 && isdigit(c) ){
119 x++;
120 }else{
121 return sqlite3_mprintf("\"%w\"", zId);
122 }
123 }
124 }
125 if( x || !sqlite3_keyword_check(zId,i) ){
126 return sqlite3_mprintf("%s", zId);
127 }
128 return sqlite3_mprintf("\"%w\"", zId);
129 }
130
131 /*
132 ** Prepare a new SQL statement. Print an error and abort if anything
133 ** goes wrong.
134 */
135 static sqlite3_stmt *sqldiff_vprepare(
136 SqlDiffCtx *p,
137 const char *zFormat,
138 va_list ap
139 ){
140 char *zSql;
141 int rc;
142 sqlite3_stmt *pStmt;
143
144 zSql = sqlite3_vmprintf(zFormat, ap);
145 if( zSql==0 ) fossil_fatal("out of memory\n");
146 rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
147 if( rc ){
148 sqldiffError(p, "SQL statement error: %s\n\"%s\"", sqlite3_errmsg(p->db),
149 zSql);
150 sqlite3_finalize(pStmt);
151 pStmt = 0;
152 }
153 sqlite3_free(zSql);
154 return pStmt;
155 }
156 static sqlite3_stmt *sqldiff_prepare(SqlDiffCtx *p, const char *zFormat, ...){
157 va_list ap;
158 sqlite3_stmt *pStmt;
159 va_start(ap, zFormat);
160 pStmt = sqldiff_vprepare(p, zFormat, ap);
161 va_end(ap);
162 return pStmt;
163 }
164
165 /*
166 ** Free a list of strings
167 */
168 static void namelistFree(char **az){
169 if( az ){
170 int i;
171 for(i=0; az[i]; i++) sqlite3_free(az[i]);
172 sqlite3_free(az);
173 }
174 }
175
176 /*
177 ** Return a list of column names [a] for the table zDb.zTab. Space to
178 ** hold the list is obtained from sqlite3_malloc() and should released
179 ** using namelistFree() when no longer needed.
180 **
181 ** Primary key columns are listed first, followed by data columns.
182 ** The number of columns in the primary key is returned in *pnPkey.
183 **
184 ** Normally [a], the "primary key" in the previous sentence is the true
185 ** primary key - the rowid or INTEGER PRIMARY KEY for ordinary tables
186 ** or the declared PRIMARY KEY for WITHOUT ROWID tables. However, if
187 ** the p->bSchemaPK flag is set, then the schema-defined PRIMARY KEY is
188 ** used in all cases. In that case, entries that have NULL values in
189 ** any of their primary key fields will be excluded from the analysis.
190 **
191 ** If the primary key for a table is the rowid but rowid is inaccessible,
192 ** then this routine returns a NULL pointer.
193 **
194 ** [a. If the lone, named table is "sqlite_schema", "rootpage" column is
195 ** omitted and the "type" and "name" columns are made to be the PK.]
196 **
197 ** Examples:
198 ** CREATE TABLE t1(a INT UNIQUE, b INTEGER, c TEXT, PRIMARY KEY(c));
199 ** *pnPKey = 1;
200 ** az = { "rowid", "a", "b", "c", 0 } // Normal case
201 ** az = { "c", "a", "b", 0 } // g.bSchemaPK==1
202 **
203 ** CREATE TABLE t2(a INT UNIQUE, b INTEGER, c TEXT, PRIMARY KEY(b));
204 ** *pnPKey = 1;
205 ** az = { "b", "a", "c", 0 }
206 **
207 ** CREATE TABLE t3(x,y,z,PRIMARY KEY(y,z));
208 ** *pnPKey = 1 // Normal case
209 ** az = { "rowid", "x", "y", "z", 0 } // Normal case
210 ** *pnPKey = 2 // g.bSchemaPK==1
211 ** az = { "y", "x", "z", 0 } // g.bSchemaPK==1
212 **
213 ** CREATE TABLE t4(x,y,z,PRIMARY KEY(y,z)) WITHOUT ROWID;
214 ** *pnPKey = 2
215 ** az = { "y", "z", "x", 0 }
216 **
217 ** CREATE TABLE t5(rowid,_rowid_,oid);
218 ** az = 0 // The rowid is not accessible
219 */
220 static char **columnNames(
221 SqlDiffCtx *p, /* Diffing context */
222 const char *zDb, /* Database ("aaa" or "bbb") to query */
223 const char *zTab, /* Name of table to return details of */
224 int *pnPKey, /* OUT: Number of PK columns */
225 int *pbRowid /* OUT: True if PK is an implicit rowid */
226 ){
227 char **az = 0; /* List of column names to be returned */
228 int naz = 0; /* Number of entries in az[] */
229 sqlite3_stmt *pStmt; /* SQL statement being run */
230 char *zPkIdxName = 0; /* Name of the PRIMARY KEY index */
231 int truePk = 0; /* PRAGMA table_info indentifies the PK to use */
232 int nPK = 0; /* Number of PRIMARY KEY columns */
233 int i, j; /* Loop counters */
234
235 if( p->bSchemaPK==0 ){
236 /* Normal case: Figure out what the true primary key is for the table.
237 ** * For WITHOUT ROWID tables, the true primary key is the same as
238 ** the schema PRIMARY KEY, which is guaranteed to be present.
239 ** * For rowid tables with an INTEGER PRIMARY KEY, the true primary
240 ** key is the INTEGER PRIMARY KEY.
241 ** * For all other rowid tables, the rowid is the true primary key.
242 */
243 pStmt = sqldiff_prepare(p, "PRAGMA %s.index_list=%Q", zDb, zTab);
244 while( SQLITE_ROW==sqlite3_step(pStmt) ){
245 if( sqlite3_stricmp((const char*)sqlite3_column_text(pStmt,3),"pk")==0 ){
246 zPkIdxName = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1));
247 break;
248 }
249 }
250 sqlite3_finalize(pStmt);
251 if( zPkIdxName ){
252 int nKey = 0;
253 int nCol = 0;
254 truePk = 0;
255 pStmt = sqldiff_prepare(p, "PRAGMA %s.index_xinfo=%Q", zDb, zPkIdxName);
256 while( SQLITE_ROW==sqlite3_step(pStmt) ){
257 nCol++;
258 if( sqlite3_column_int(pStmt,5) ){ nKey++; continue; }
259 if( sqlite3_column_int(pStmt,1)>=0 ) truePk = 1;
260 }
261 if( nCol==nKey ) truePk = 1;
262 if( truePk ){
263 nPK = nKey;
264 }else{
265 nPK = 1;
266 }
267 sqlite3_finalize(pStmt);
268 sqlite3_free(zPkIdxName);
269 }else{
270 truePk = 1;
271 nPK = 1;
272 }
273 pStmt = sqldiff_prepare(p, "PRAGMA %s.table_info=%Q", zDb, zTab);
274 }else{
275 /* The p->bSchemaPK==1 case: Use whatever primary key is declared
276 ** in the schema. The "rowid" will still be used as the primary key
277 ** if the table definition does not contain a PRIMARY KEY.
278 */
279 nPK = 0;
280 pStmt = sqldiff_prepare(p, "PRAGMA %s.table_info=%Q", zDb, zTab);
281 while( SQLITE_ROW==sqlite3_step(pStmt) ){
282 if( sqlite3_column_int(pStmt,5)>0 ) nPK++;
283 }
284 sqlite3_reset(pStmt);
285 if( nPK==0 ) nPK = 1;
286 truePk = 1;
287 }
288 if( p->bSchemaCompare ){
289 assert( sqlite3_stricmp(zTab,"sqlite_schema")==0
290 || sqlite3_stricmp(zTab,"sqlite_master")==0 );
291 /* For sqlite_schema, will use type and name as the PK. */
292 nPK = 2;
293 truePk = 0;
294 }
295 *pnPKey = nPK;
296 naz = nPK;
297 az = sqlite3_malloc( sizeof(char*)*(nPK+1) );
298 if( az==0 ) fossil_fatal("out of memory\n");
299 memset(az, 0, sizeof(char*)*(nPK+1));
300 if( p->bSchemaCompare ){
301 az[0] = sqlite3_mprintf("%s", "type");
302 az[1] = sqlite3_mprintf("%s", "name");
303 }
304 while( SQLITE_ROW==sqlite3_step(pStmt) ){
305 char * sid = safeId((char*)sqlite3_column_text(pStmt,1));
306 int iPKey;
307 if( truePk && (iPKey = sqlite3_column_int(pStmt,5))>0 ){
308 az[iPKey-1] = sid;
309 }else{
310 if( !p->bSchemaCompare
311 || !(strcmp(sid,"rootpage")==0
312 ||strcmp(sid,"name")==0
313 ||strcmp(sid,"type")==0)){
314 az = sqlite3_realloc(az, sizeof(char*)*(naz+2) );
315 if( az==0 ) fossil_fatal("out of memory\n");
316 az[naz++] = sid;
317 }
318 }
319 }
320 sqlite3_finalize(pStmt);
321 if( az ) az[naz] = 0;
322
323 /* If it is non-NULL, set *pbRowid to indicate whether or not the PK of
324 ** this table is an implicit rowid (*pbRowid==1) or not (*pbRowid==0). */
325 if( pbRowid ) *pbRowid = (az[0]==0);
326
327 /* If this table has an implicit rowid for a PK, figure out how to refer
328 ** to it. There are usually three options - "rowid", "_rowid_" and "oid".
329 ** Any of these will work, unless the table has an explicit column of the
330 ** same name or the sqlite_schema tables are to be compared. In the latter
331 ** case, pretend that the "true" primary key is the name column, which
332 ** avoids extraneous diffs against the schemas due to rowid variance. */
333 if( az[0]==0 ){
334 const char *azRowid[] = { "rowid", "_rowid_", "oid" };
335 for(i=0; i<sizeof(azRowid)/sizeof(azRowid[0]); i++){
336 for(j=1; j<naz; j++){
337 if( sqlite3_stricmp(az[j], azRowid[i])==0 ) break;
338 }
339 if( j>=naz ){
340 az[0] = sqlite3_mprintf("%s", azRowid[i]);
341 break;
342 }
343 }
344 if( az[0]==0 ){
345 for(i=1; i<naz; i++) sqlite3_free(az[i]);
346 sqlite3_free(az);
347 az = 0;
348 }
349 }
350 return az;
351 }
352
353 /*
354 ** Print the sqlite3_value X as an SQL literal.
355 */
356 static void printQuoted(Blob *out, sqlite3_value *X){
357 switch( sqlite3_value_type(X) ){
358 case SQLITE_FLOAT: {
359 double r1;
360 char zBuf[50];
361 r1 = sqlite3_value_double(X);
362 sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
363 blob_appendf(out, "%s", zBuf);
364 break;
365 }
366 case SQLITE_INTEGER: {
367 blob_appendf(out, "%lld", sqlite3_value_int64(X));
368 break;
369 }
370 case SQLITE_BLOB: {
371 const unsigned char *zBlob = sqlite3_value_blob(X);
372 int nBlob = sqlite3_value_bytes(X);
373 if( zBlob ){
374 int i;
375 blob_appendf(out, "x'");
376 for(i=0; i<nBlob; i++){
377 blob_appendf(out, "%02x", zBlob[i]);
378 }
379 blob_appendf(out, "'");
380 }else{
381 /* Could be an OOM, could be a zero-byte blob */
382 blob_appendf(out, "X''");
383 }
384 break;
385 }
386 case SQLITE_TEXT: {
387 const unsigned char *zArg = sqlite3_value_text(X);
388
389 if( zArg==0 ){
390 blob_appendf(out, "NULL");
391 }else{
392 int inctl = 0;
393 int i, j;
394 blob_appendf(out, "'");
395 for(i=j=0; zArg[i]; i++){
396 char c = zArg[i];
397 int ctl = iscntrl((unsigned char)c);
398 if( ctl>inctl ){
399 inctl = ctl;
400 blob_appendf(out, "%.*s'||X'%02x", i-j, &zArg[j], c);
401 j = i+1;
402 }else if( ctl ){
403 blob_appendf(out, "%02x", c);
404 j = i+1;
405 }else{
406 if( inctl ){
407 inctl = 0;
408 blob_appendf(out, "'\n||'");
409 }
410 if( c=='\'' ){
411 blob_appendf(out, "%.*s'", i-j+1, &zArg[j]);
412 j = i+1;
413 }
414 }
415 }
416 blob_appendf(out, "%s'", &zArg[j]);
417 }
418 break;
419 }
420 case SQLITE_NULL: {
421 blob_appendf(out, "NULL");
422 break;
423 }
424 }
425 }
426
427 /*
428 ** Output SQL that will recreate the bbb.zTab table.
429 */
430 static void dump_table(SqlDiffCtx *p, const char *zTab){
431 char *zId = safeId(zTab); /* Name of the table */
432 char **az = 0; /* List of columns */
433 int nPk; /* Number of true primary key columns */
434 int nCol; /* Number of data columns */
435 int i; /* Loop counter */
436 sqlite3_stmt *pStmt; /* SQL statement */
437 const char *zSep; /* Separator string */
438 sqlite3_str *pIns; /* Beginning of the INSERT statement */
439
440 pStmt = sqldiff_prepare(p,
441 "SELECT sql FROM bbb.sqlite_schema WHERE name=%Q", zTab);
442 if( SQLITE_ROW==sqlite3_step(pStmt) ){
443 blob_appendf(p->out, "%s;\n", sqlite3_column_text(pStmt,0));
444 }
445 sqlite3_finalize(pStmt);
446 if( !p->bSchemaOnly ){
447 az = columnNames(p, "bbb", zTab, &nPk, 0);
448 pIns = sqlite3_str_new(0);
449 if( az==0 ){
450 pStmt = sqldiff_prepare(p, "SELECT * FROM bbb.%s", zId);
451 sqlite3_str_appendf(pIns,"INSERT INTO %s VALUES", zId);
452 }else{
453 sqlite3_str *pSql = sqlite3_str_new(0);
454 zSep = "SELECT";
455 for(i=0; az[i]; i++){
456 sqlite3_str_appendf(pSql, "%s %s", zSep, az[i]);
457 zSep = ",";
458 }
459 sqlite3_str_appendf(pSql," FROM bbb.%s", zId);
460 zSep = " ORDER BY";
461 for(i=1; i<=nPk; i++){
462 sqlite3_str_appendf(pSql, "%s %d", zSep, i);
463 zSep = ",";
464 }
465 pStmt = sqldiff_prepare(p, "%s", sqlite3_str_value(pSql));
466 strFree(pSql);
467 sqlite3_str_appendf(pIns, "INSERT INTO %s", zId);
468 zSep = "(";
469 for(i=0; az[i]; i++){
470 sqlite3_str_appendf(pIns, "%s%s", zSep, az[i]);
471 zSep = ",";
472 }
473 sqlite3_str_appendf(pIns,") VALUES");
474 namelistFree(az);
475 }
476 nCol = sqlite3_column_count(pStmt);
477 while( SQLITE_ROW==sqlite3_step(pStmt) ){
478 blob_appendf(p->out, "%s",sqlite3_str_value(pIns));
479 zSep = "(";
480 for(i=0; i<nCol; i++){
481 blob_appendf(p->out, "%s",zSep);
482 printQuoted(p->out, sqlite3_column_value(pStmt,i));
483 zSep = ",";
484 }
485 blob_appendf(p->out, ");\n");
486 }
487 sqlite3_finalize(pStmt);
488 strFree(pIns);
489 } /* endif !p->bSchemaOnly */
490 pStmt = sqldiff_prepare(p, "SELECT sql FROM bbb.sqlite_schema"
491 " WHERE type='index' AND tbl_name=%Q AND sql IS NOT NULL",
492 zTab);
493 while( SQLITE_ROW==sqlite3_step(pStmt) ){
494 blob_appendf(p->out, "%s;\n", sqlite3_column_text(pStmt,0));
495 }
496 sqlite3_finalize(pStmt);
497 sqlite3_free(zId);
498 }
499
500
501 /*
502 ** Compute all differences for a single table, except if the
503 ** table name is sqlite_schema, ignore the rootpage column.
504 */
505 static void diff_one_table(SqlDiffCtx *p, const char *zTab){
506 char *zId = safeId(zTab); /* Name of table (translated for us in SQL) */
507 char **az = 0; /* Columns in aaa */
508 char **az2 = 0; /* Columns in bbb */
509 int nPk; /* Primary key columns in aaa */
510 int nPk2; /* Primary key columns in bbb */
511 int n = 0; /* Number of columns in aaa */
512 int n2; /* Number of columns in bbb */
513 int nQ; /* Number of output columns in the diff query */
514 int i; /* Loop counter */
515 const char *zSep; /* Separator string */
516 sqlite3_str *pSql; /* Comparison query */
517 sqlite3_stmt *pStmt; /* Query statement to do the diff */
518 const char *zLead = /* Becomes line-comment for sqlite_schema */
519 (p->bSchemaCompare)? "-- " : "";
520
521 pSql = sqlite3_str_new(0);
522 if( p->fDebug==SQLDIFF_COLUMN_NAMES ){
523 /* Simply run columnNames() on all tables of the origin
524 ** database and show the results. This is used for testing
525 ** and debugging of the columnNames() function.
526 */
527 az = columnNames(p, "bbb",zTab, &nPk, 0);
528 if( az==0 ){
529 fossil_print("Rowid not accessible for %s\n", zId);
530 }else{
531 fossil_print("%s:", zId);
532 for(i=0; az[i]; i++){
533 fossil_print(" %s", az[i]);
534 if( i+1==nPk ) fossil_print(" *");
535 }
536 fossil_print("\n");
537 }
538 goto end_diff_one_table;
539 }
540
541 if( sqlite3_table_column_metadata(p->db,"bbb",zTab,0,0,0,0,0,0) ){
542 if( !sqlite3_table_column_metadata(p->db,"aaa",zTab,0,0,0,0,0,0) ){
543 /* Table missing from second database. */
544 if( p->bSchemaCompare ){
545 blob_appendf(p->out, "-- 2nd DB has no %s table\n", zTab);
546 }else{
547 blob_appendf(p->out, "DROP TABLE %s;\n", zId);
548 }
549 }
550 goto end_diff_one_table;
551 }
552
553 if( sqlite3_table_column_metadata(p->db,"aaa",zTab,0,0,0,0,0,0) ){
554 /* Table missing from source */
555 if( p->bSchemaCompare ){
556 blob_appendf(p->out, "-- 1st DB has no %s table\n", zTab);
557 }else{
558 dump_table(p, zTab);
559 }
560 goto end_diff_one_table;
561 }
562
563 az = columnNames(p, "aaa", zTab, &nPk, 0);
564 az2 = columnNames(p, "bbb", zTab, &nPk2, 0);
565 if( az && az2 ){
566 for(n=0; az[n] && az2[n]; n++){
567 if( sqlite3_stricmp(az[n],az2[n])!=0 ) break;
568 }
569 }
570 if( az==0
571 || az2==0
572 || nPk!=nPk2
573 || az[n]
574 ){
575 /* Schema mismatch */
576 blob_appendf(p->out, "%sDROP TABLE %s; -- due to schema mismatch\n",
577 zLead, zId);
578 dump_table(p, zTab);
579 goto end_diff_one_table;
580 }
581
582 /* Build the comparison query */
583 for(n2=n; az2[n2]; n2++){
584 char *zNTab = safeId(az2[n2]);
585 blob_appendf(p->out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zNTab);
586 sqlite3_free(zNTab);
587 }
588 nQ = nPk2+1+2*(n2-nPk2);
589 if( n2>nPk2 ){
590 zSep = "SELECT ";
591 for(i=0; i<nPk; i++){
592 sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
593 zSep = ", ";
594 }
595 sqlite3_str_appendf(pSql, ", 1 /* changed row */");
596 while( az[i] ){
597 sqlite3_str_appendf(pSql, ", A.%s IS NOT B.%s, B.%s",
598 az[i], az2[i], az2[i]);
599 i++;
600 }
601 while( az2[i] ){
602 sqlite3_str_appendf(pSql, ", B.%s IS NOT NULL, B.%s",
603 az2[i], az2[i]);
604 i++;
605 }
606 sqlite3_str_appendf(pSql, "\n FROM aaa.%s A, bbb.%s B\n", zId, zId);
607 zSep = " WHERE";
608 for(i=0; i<nPk; i++){
609 sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
610 zSep = " AND";
611 }
612 zSep = "\n AND (";
613 while( az[i] ){
614 sqlite3_str_appendf(pSql, "%sA.%s IS NOT B.%s%s\n",
615 zSep, az[i], az2[i], az2[i+1]==0 ? ")" : "");
616 zSep = " OR ";
617 i++;
618 }
619 while( az2[i] ){
620 sqlite3_str_appendf(pSql, "%sB.%s IS NOT NULL%s\n",
621 zSep, az2[i], az2[i+1]==0 ? ")" : "");
622 zSep = " OR ";
623 i++;
624 }
625 sqlite3_str_appendf(pSql, " UNION ALL\n");
626 }
627 zSep = "SELECT ";
628 for(i=0; i<nPk; i++){
629 sqlite3_str_appendf(pSql, "%sA.%s", zSep, az[i]);
630 zSep = ", ";
631 }
632 sqlite3_str_appendf(pSql, ", 2 /* deleted row */");
633 while( az2[i] ){
634 sqlite3_str_appendf(pSql, ", NULL, NULL");
635 i++;
636 }
637 sqlite3_str_appendf(pSql, "\n FROM aaa.%s A\n", zId);
638 sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM bbb.%s B\n", zId);
639 zSep = " WHERE";
640 for(i=0; i<nPk; i++){
641 sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
642 zSep = " AND";
643 }
644 sqlite3_str_appendf(pSql, ")\n");
645 zSep = " UNION ALL\nSELECT ";
646 for(i=0; i<nPk; i++){
647 sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
648 zSep = ", ";
649 }
650 sqlite3_str_appendf(pSql, ", 3 /* inserted row */");
651 while( az2[i] ){
652 sqlite3_str_appendf(pSql, ", 1, B.%s", az2[i]);
653 i++;
654 }
655 sqlite3_str_appendf(pSql, "\n FROM bbb.%s B\n", zId);
656 sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM aaa.%s A\n", zId);
657 zSep = " WHERE";
658 for(i=0; i<nPk; i++){
659 sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
660 zSep = " AND";
661 }
662 sqlite3_str_appendf(pSql, ")\n ORDER BY");
663 zSep = " ";
664 for(i=1; i<=nPk; i++){
665 sqlite3_str_appendf(pSql, "%s%d", zSep, i);
666 zSep = ", ";
667 }
668 sqlite3_str_appendf(pSql, ";\n");
669
670 if( p->fDebug & SQLDIFF_DIFF_SQL ){
671 fossil_print("SQL for %s:\n%s\n", zId, sqlite3_str_value(pSql));
672 goto end_diff_one_table;
673 }
674
675 /* Drop indexes that are missing in the destination */
676 pStmt = sqldiff_prepare(p,
677 "SELECT name FROM aaa.sqlite_schema"
678 " WHERE type='index' AND tbl_name=%Q"
679 " AND sql IS NOT NULL"
680 " AND sql NOT IN (SELECT sql FROM bbb.sqlite_schema"
681 " WHERE type='index' AND tbl_name=%Q"
682 " AND sql IS NOT NULL)",
683 zTab, zTab);
684 while( SQLITE_ROW==sqlite3_step(pStmt) ){
685 char *z = safeId((const char*)sqlite3_column_text(pStmt,0));
686 blob_appendf(p->out, "DROP INDEX %s;\n", z);
687 sqlite3_free(z);
688 }
689 sqlite3_finalize(pStmt);
690
691 /* Run the query and output differences */
692 if( !p->bSchemaOnly ){
693 pStmt = sqldiff_prepare(p, "%s", sqlite3_str_value(pSql));
694 while( SQLITE_ROW==sqlite3_step(pStmt) ){
695 int iType = sqlite3_column_int(pStmt, nPk);
696 if( iType==1 || iType==2 ){
697 if( iType==1 ){ /* Change the content of a row */
698 blob_appendf(p->out, "%sUPDATE %s", zLead, zId);
699 zSep = " SET";
700 for(i=nPk+1; i<nQ; i+=2){
701 if( sqlite3_column_int(pStmt,i)==0 ) continue;
702 blob_appendf(p->out, "%s %s=", zSep, az2[(i+nPk-1)/2]);
703 zSep = ",";
704 printQuoted(p->out, sqlite3_column_value(pStmt,i+1));
705 }
706 }else{ /* Delete a row */
707 blob_appendf(p->out, "%sDELETE FROM %s", zLead, zId);
708 }
709 zSep = " WHERE";
710 for(i=0; i<nPk; i++){
711 blob_appendf(p->out, "%s %s=", zSep, az2[i]);
712 printQuoted(p->out, sqlite3_column_value(pStmt,i));
713 zSep = " AND";
714 }
715 blob_appendf(p->out, ";\n");
716 }else{ /* Insert a row */
717 blob_appendf(p->out, "%sINSERT INTO %s(%s", zLead, zId, az2[0]);
718 for(i=1; az2[i]; i++) blob_appendf(p->out, ",%s", az2[i]);
719 blob_appendf(p->out, ") VALUES");
720 zSep = "(";
721 for(i=0; i<nPk2; i++){
722 blob_appendf(p->out, "%s", zSep);
723 zSep = ",";
724 printQuoted(p->out, sqlite3_column_value(pStmt,i));
725 }
726 for(i=nPk2+2; i<nQ; i+=2){
727 blob_appendf(p->out, ",");
728 printQuoted(p->out, sqlite3_column_value(pStmt,i));
729 }
730 blob_appendf(p->out, ");\n");
731 }
732 }
733 sqlite3_finalize(pStmt);
734 } /* endif !p->bSchemaOnly */
735
736 /* Create indexes that are missing in the source */
737 pStmt = sqldiff_prepare(p,
738 "SELECT sql FROM bbb.sqlite_schema"
739 " WHERE type='index' AND tbl_name=%Q"
740 " AND sql IS NOT NULL"
741 " AND sql NOT IN (SELECT sql FROM aaa.sqlite_schema"
742 " WHERE type='index' AND tbl_name=%Q"
743 " AND sql IS NOT NULL)",
744 zTab, zTab);
745 while( SQLITE_ROW==sqlite3_step(pStmt) ){
746 blob_appendf(p->out, "%s;\n", sqlite3_column_text(pStmt,0));
747 }
748 sqlite3_finalize(pStmt);
749
750 end_diff_one_table:
751 strFree(pSql);
752 sqlite3_free(zId);
753 namelistFree(az);
754 namelistFree(az2);
755 return;
756 }
757
758 #if 0
759 /*
760 ** Check that table zTab exists and has the same schema in both the "aaa"
761 ** and "bbb" databases currently opened by the global db handle. If they
762 ** do not, output an error message on stderr and exit(1). Otherwise, if
763 ** the schemas do match, return control to the caller.
764 */
765 static void checkSchemasMatch(SqlDiffCtx *p, const char *zTab){
766 sqlite3_stmt *pStmt = sqldiff_prepare(p,
767 "SELECT A.sql=B.sql FROM aaa.sqlite_schema A, bbb.sqlite_schema B"
768 " WHERE A.name=%Q AND B.name=%Q", zTab, zTab
769 );
770 if( SQLITE_ROW==sqlite3_step(pStmt) ){
771 if( sqlite3_column_int(pStmt,0)==0 ){
772 sqldiffError(p, "schema changes for table %s", safeId(zTab));
773 }
774 }else{
775 sqldiffError(p, "table %s missing from one or both databases",safeId(zTab));
776 }
777 sqlite3_finalize(pStmt);
778 }
779 #endif
780
781 /*
782 ** Return true if the ascii character passed as the only argument is a
783 ** whitespace character. Otherwise return false.
784 */
785 static int is_whitespace(char x){
786 return (x==' ' || x=='\t' || x=='\n' || x=='\r');
787 }
788
789 /*
790 ** Extract the next SQL keyword or quoted string from buffer zIn and copy it
791 ** (or a prefix of it if it will not fit) into buffer zBuf, size nBuf bytes.
792 ** Return a pointer to the character within zIn immediately following
793 ** the token or quoted string just extracted.
794 */
795 static const char *gobble_token(const char *zIn, char *zBuf, int nBuf){
796 const char *p = zIn;
797 char *pOut = zBuf;
798 char *pEnd = &pOut[nBuf-1];
799 char q = 0; /* quote character, if any */
800
801 if( p==0 ) return 0;
802 while( is_whitespace(*p) ) p++;
803 switch( *p ){
804 case '"': q = '"'; break;
805 case '\'': q = '\''; break;
806 case '`': q = '`'; break;
807 case '[': q = ']'; break;
808 }
809
810 if( q ){
811 p++;
812 while( *p && pOut<pEnd ){
813 if( *p==q ){
814 p++;
815 if( *p!=q ) break;
816 }
817 if( pOut<pEnd ) *pOut++ = *p;
818 p++;
819 }
820 }else{
821 while( *p && !is_whitespace(*p) && *p!='(' ){
822 if( pOut<pEnd ) *pOut++ = *p;
823 p++;
824 }
825 }
826
827 *pOut = '\0';
828 return p;
829 }
830
831 /*
832 ** This function is the implementation of SQL scalar function "module_name":
833 **
834 ** module_name(SQL)
835 **
836 ** The only argument should be an SQL statement of the type that may appear
837 ** in the sqlite_schema table. If the statement is a "CREATE VIRTUAL TABLE"
838 ** statement, then the value returned is the name of the module that it
839 ** uses. Otherwise, if the statement is not a CVT, NULL is returned.
840 */
841 static void module_name_func(
842 sqlite3_context *pCtx,
843 int nVal, sqlite3_value **apVal
844 ){
845 const char *zSql;
846 char zToken[32];
847
848 assert( nVal==1 );
849 zSql = (const char*)sqlite3_value_text(apVal[0]);
850
851 zSql = gobble_token(zSql, zToken, sizeof(zToken));
852 if( zSql==0 || sqlite3_stricmp(zToken, "create") ) return;
853 zSql = gobble_token(zSql, zToken, sizeof(zToken));
854 if( zSql==0 || sqlite3_stricmp(zToken, "virtual") ) return;
855 zSql = gobble_token(zSql, zToken, sizeof(zToken));
856 if( zSql==0 || sqlite3_stricmp(zToken, "table") ) return;
857 zSql = gobble_token(zSql, zToken, sizeof(zToken));
858 if( zSql==0 ) return;
859 zSql = gobble_token(zSql, zToken, sizeof(zToken));
860 if( zSql==0 || sqlite3_stricmp(zToken, "using") ) return;
861 zSql = gobble_token(zSql, zToken, sizeof(zToken));
862
863 sqlite3_result_text(pCtx, zToken, -1, SQLITE_TRANSIENT);
864 }
865
866 /*
867 ** Return the text of an SQL statement that itself returns the list of
868 ** tables to process within the database.
869 */
870 const char *all_tables_sql(SqlDiffCtx *p){
871 if( p->bHandleVtab ){
872 int rc;
873
874 rc = sqlite3_exec(p->db,
875 "CREATE TEMP TABLE tblmap(module COLLATE nocase, postfix);"
876 "INSERT INTO temp.tblmap VALUES"
877 "('fts3', '_content'), ('fts3', '_segments'), ('fts3', '_segdir'),"
878
879 "('fts4', '_content'), ('fts4', '_segments'), ('fts4', '_segdir'),"
880 "('fts4', '_docsize'), ('fts4', '_stat'),"
881
882 "('fts5', '_data'), ('fts5', '_idx'), ('fts5', '_content'),"
883 "('fts5', '_docsize'), ('fts5', '_config'),"
884
885 "('rtree', '_node'), ('rtree', '_rowid'), ('rtree', '_parent');"
886 , 0, 0, 0
887 );
888 assert( rc==SQLITE_OK );
889
890 rc = sqlite3_create_function(
891 p->db, "module_name", 1, SQLITE_UTF8, 0, module_name_func, 0, 0
892 );
893 assert( rc==SQLITE_OK );
894
895 return
896 "SELECT name FROM aaa.sqlite_schema\n"
897 " WHERE type='table' AND (\n"
898 " module_name(sql) IS NULL OR \n"
899 " module_name(sql) IN (SELECT module FROM temp.tblmap)\n"
900 " ) AND name NOT IN (\n"
901 " SELECT a.name || b.postfix \n"
902 "FROM aaa.sqlite_schema AS a, temp.tblmap AS b \n"
903 "WHERE module_name(a.sql) = b.module\n"
904 " )\n"
905 "UNION \n"
906 "SELECT name FROM bbb.sqlite_schema\n"
907 " WHERE type='table' AND (\n"
908 " module_name(sql) IS NULL OR \n"
909 " module_name(sql) IN (SELECT module FROM temp.tblmap)\n"
910 " ) AND name NOT IN (\n"
911 " SELECT a.name || b.postfix \n"
912 "FROM bbb.sqlite_schema AS a, temp.tblmap AS b \n"
913 "WHERE module_name(a.sql) = b.module\n"
914 " )\n"
915 " ORDER BY name";
916 }else{
917 return
918 "SELECT name FROM aaa.sqlite_schema\n"
919 " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
920 " UNION\n"
921 "SELECT name FROM bbb.sqlite_schema\n"
922 " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
923 " ORDER BY name";
924 }
925 }
926
927 /*
928 ** Check to see if the two input blobs, pA and pB, are both
929 ** SQLite database files. If they are, then output an SQL diff
930 ** into pOut and return true. If either of the inputs is not
931 ** a well-formed SQLite database, then return 0.
932 **
933 ** A semantic-level diff is computed. In other words, it is the
934 ** content of the database that matters. If the databases have
935 ** different page sizes or text representations or if the pages
936 ** are in a different order, that does not affect the output.
937 ** Only content differences are shown.
938 */
939 int sqldiff(
940 Blob *pA, /* FROM file */
941 Blob *pB, /* TO file */
942 Blob *pOut, /* Write diff here */
943 DiffConfig *pCfg /* Configuration options */
944 ){
945 SqlDiffCtx s;
946 sqlite3_stmt *pStmt;
947 int rc;
948 u8 *aA, *aB;
949 int szA, szB;
950 u8 aModeA[2];
951 u8 aModeB[2];
952
953 if( pOut==0 ) return 0;
954 if( !looks_like_sqlite_db(pA) ) return 0;
955 if( !looks_like_sqlite_db(pB) ) return 0;
956 memset(&s, 0, sizeof(s));
957 s.out = pOut;
958 rc = sqlite3_open(":memory:", &s.db);
959 if( rc ){
960 fossil_fatal("Unable to open an auxiliary in-memory database\n");
961 }
962 rc = sqlite3_exec(s.db, "ATTACH ':memory:' AS aaa;", 0, 0, 0);
963 if( rc ){
964 fossil_fatal("Unable to attach an in-memory database\n");
965 }
966 rc = sqlite3_exec(s.db, "ATTACH ':memory:' AS bbb;", 0, 0, 0);
967 if( rc ){
968 fossil_fatal("Unable to attach an in-memory database\n");
969 }
970 aA = (u8*)blob_buffer(pA);
971 szA = blob_size(pA);
972 memcpy(aModeA, &aA[18], 2);
973 aA[18] = aA[19] = 1;
974 aB = (u8*)blob_buffer(pB);
975 szB = blob_size(pB);
976 memcpy(aModeB, &aB[18], 2);
977 aB[18] = aB[19] = 1;
978 rc = sqlite3_deserialize(s.db, "aaa", aA, szA, szA,
979 SQLITE_DESERIALIZE_READONLY);
980 if( rc ){
981 s.nErr++;
982 goto not_a_valid_diff;
983 }
984 rc = sqlite3_deserialize(s.db, "bbb", aB, szB, szB,
985 SQLITE_DESERIALIZE_READONLY);
986 if( rc ){
987 s.nErr++;
988 goto not_a_valid_diff;
989 }
990 if( pCfg->diffFlags & DIFF_HTML ) blob_appendf(pOut, "<pre>\n");
991 pStmt = sqldiff_prepare(&s, "%s", all_tables_sql(&s) );
992 if( pStmt ){
993 while( SQLITE_ROW==sqlite3_step(pStmt) ){
994 diff_one_table(&s, (const char*)sqlite3_column_text(pStmt,0));
995 }
996 sqlite3_finalize(pStmt);
997 }
998 if( pCfg->diffFlags & DIFF_HTML ) blob_appendf(pOut, "</pre>\n");
999
1000 not_a_valid_diff:
1001 sqlite3_close(s.db);
1002 if( s.nErr ) blob_reset(pOut);
1003 memcpy(&aA[18], aModeA, 2);
1004 memcpy(&aB[18], aModeB, 2);
1005 return s.nErr==0;
1006 }
--- tools/makemake.tcl
+++ tools/makemake.tcl
@@ -165,10 +165,11 @@
165165
shun
166166
sitemap
167167
skins
168168
smtp
169169
sqlcmd
170
+ sqldiff
170171
stash
171172
stat
172173
statrep
173174
style
174175
sync
175176
--- tools/makemake.tcl
+++ tools/makemake.tcl
@@ -165,10 +165,11 @@
165 shun
166 sitemap
167 skins
168 smtp
169 sqlcmd
 
170 stash
171 stat
172 statrep
173 style
174 sync
175
--- tools/makemake.tcl
+++ tools/makemake.tcl
@@ -165,10 +165,11 @@
165 shun
166 sitemap
167 skins
168 smtp
169 sqlcmd
170 sqldiff
171 stash
172 stat
173 statrep
174 style
175 sync
176
+10 -4
--- win/Makefile.dmc
+++ win/Makefile.dmc
@@ -32,13 +32,13 @@
3232
3333
SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen
3434
3535
PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000
3636
37
-SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c
37
+SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c sqldiff_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c
3838
39
-OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O
39
+OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\sqldiff$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O
4040
4141
4242
RC=$(DMDIR)\bin\rcc
4343
RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__
4444
@@ -53,11 +53,11 @@
5353
5454
$(OBJDIR)\fossil.res: $B\win\fossil.rc
5555
$(RC) $(RCFLAGS) -o$@ $**
5656
5757
$(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
58
- +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@
58
+ +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd sqldiff stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@
5959
+echo fossil >> $@
6060
+echo fossil >> $@
6161
+echo $(LIBS) >> $@
6262
+echo. >> $@
6363
+echo fossil >> $@
@@ -833,10 +833,16 @@
833833
$(OBJDIR)\sqlcmd$O : sqlcmd_.c sqlcmd.h
834834
$(TCC) -o$@ -c sqlcmd_.c
835835
836836
sqlcmd_.c : $(SRCDIR)\sqlcmd.c
837837
+translate$E $** > $@
838
+
839
+$(OBJDIR)\sqldiff$O : sqldiff_.c sqldiff.h
840
+ $(TCC) -o$@ -c sqldiff_.c
841
+
842
+sqldiff_.c : $(SRCDIR)\sqldiff.c
843
+ +translate$E $** > $@
838844
839845
$(OBJDIR)\stash$O : stash_.c stash.h
840846
$(TCC) -o$@ -c stash_.c
841847
842848
stash_.c : $(SRCDIR)\stash.c
@@ -1009,7 +1015,7 @@
10091015
10101016
zip_.c : $(SRCDIR)\zip.c
10111017
+translate$E $** > $@
10121018
10131019
headers: makeheaders$E page_index.h builtin_data.h VERSION.h
1014
- +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h
1020
+ +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h sqldiff_.c:sqldiff.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h
10151021
@copy /Y nul: headers
10161022
--- win/Makefile.dmc
+++ win/Makefile.dmc
@@ -32,13 +32,13 @@
32
33 SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen
34
35 PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000
36
37 SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c
38
39 OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O
40
41
42 RC=$(DMDIR)\bin\rcc
43 RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__
44
@@ -53,11 +53,11 @@
53
54 $(OBJDIR)\fossil.res: $B\win\fossil.rc
55 $(RC) $(RCFLAGS) -o$@ $**
56
57 $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
58 +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@
59 +echo fossil >> $@
60 +echo fossil >> $@
61 +echo $(LIBS) >> $@
62 +echo. >> $@
63 +echo fossil >> $@
@@ -833,10 +833,16 @@
833 $(OBJDIR)\sqlcmd$O : sqlcmd_.c sqlcmd.h
834 $(TCC) -o$@ -c sqlcmd_.c
835
836 sqlcmd_.c : $(SRCDIR)\sqlcmd.c
837 +translate$E $** > $@
 
 
 
 
 
 
838
839 $(OBJDIR)\stash$O : stash_.c stash.h
840 $(TCC) -o$@ -c stash_.c
841
842 stash_.c : $(SRCDIR)\stash.c
@@ -1009,7 +1015,7 @@
1009
1010 zip_.c : $(SRCDIR)\zip.c
1011 +translate$E $** > $@
1012
1013 headers: makeheaders$E page_index.h builtin_data.h VERSION.h
1014 +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h
1015 @copy /Y nul: headers
1016
--- win/Makefile.dmc
+++ win/Makefile.dmc
@@ -32,13 +32,13 @@
32
33 SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen
34
35 PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000
36
37 SRC = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c sqldiff_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c
38
39 OBJ = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\sqldiff$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O
40
41
42 RC=$(DMDIR)\bin\rcc
43 RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__
44
@@ -53,11 +53,11 @@
53
54 $(OBJDIR)\fossil.res: $B\win\fossil.rc
55 $(RC) $(RCFLAGS) -o$@ $**
56
57 $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
58 +echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd sqldiff stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@
59 +echo fossil >> $@
60 +echo fossil >> $@
61 +echo $(LIBS) >> $@
62 +echo. >> $@
63 +echo fossil >> $@
@@ -833,10 +833,16 @@
833 $(OBJDIR)\sqlcmd$O : sqlcmd_.c sqlcmd.h
834 $(TCC) -o$@ -c sqlcmd_.c
835
836 sqlcmd_.c : $(SRCDIR)\sqlcmd.c
837 +translate$E $** > $@
838
839 $(OBJDIR)\sqldiff$O : sqldiff_.c sqldiff.h
840 $(TCC) -o$@ -c sqldiff_.c
841
842 sqldiff_.c : $(SRCDIR)\sqldiff.c
843 +translate$E $** > $@
844
845 $(OBJDIR)\stash$O : stash_.c stash.h
846 $(TCC) -o$@ -c stash_.c
847
848 stash_.c : $(SRCDIR)\stash.c
@@ -1009,7 +1015,7 @@
1015
1016 zip_.c : $(SRCDIR)\zip.c
1017 +translate$E $** > $@
1018
1019 headers: makeheaders$E page_index.h builtin_data.h VERSION.h
1020 +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h interwiki_.c:interwiki.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h sqldiff_.c:sqldiff.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h
1021 @copy /Y nul: headers
1022
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -518,10 +518,11 @@
518518
$(SRCDIR)/shun.c \
519519
$(SRCDIR)/sitemap.c \
520520
$(SRCDIR)/skins.c \
521521
$(SRCDIR)/smtp.c \
522522
$(SRCDIR)/sqlcmd.c \
523
+ $(SRCDIR)/sqldiff.c \
523524
$(SRCDIR)/stash.c \
524525
$(SRCDIR)/stat.c \
525526
$(SRCDIR)/statrep.c \
526527
$(SRCDIR)/style.c \
527528
$(SRCDIR)/sync.c \
@@ -782,10 +783,11 @@
782783
$(OBJDIR)/shun_.c \
783784
$(OBJDIR)/sitemap_.c \
784785
$(OBJDIR)/skins_.c \
785786
$(OBJDIR)/smtp_.c \
786787
$(OBJDIR)/sqlcmd_.c \
788
+ $(OBJDIR)/sqldiff_.c \
787789
$(OBJDIR)/stash_.c \
788790
$(OBJDIR)/stat_.c \
789791
$(OBJDIR)/statrep_.c \
790792
$(OBJDIR)/style_.c \
791793
$(OBJDIR)/sync_.c \
@@ -931,10 +933,11 @@
931933
$(OBJDIR)/shun.o \
932934
$(OBJDIR)/sitemap.o \
933935
$(OBJDIR)/skins.o \
934936
$(OBJDIR)/smtp.o \
935937
$(OBJDIR)/sqlcmd.o \
938
+ $(OBJDIR)/sqldiff.o \
936939
$(OBJDIR)/stash.o \
937940
$(OBJDIR)/stat.o \
938941
$(OBJDIR)/statrep.o \
939942
$(OBJDIR)/style.o \
940943
$(OBJDIR)/sync.o \
@@ -1284,10 +1287,11 @@
12841287
$(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
12851288
$(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
12861289
$(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
12871290
$(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \
12881291
$(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h \
1292
+ $(OBJDIR)/sqldiff_.c:$(OBJDIR)/sqldiff.h \
12891293
$(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \
12901294
$(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \
12911295
$(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \
12921296
$(OBJDIR)/style_.c:$(OBJDIR)/style.h \
12931297
$(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \
@@ -2266,10 +2270,18 @@
22662270
22672271
$(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h
22682272
$(XTCC) -o $(OBJDIR)/sqlcmd.o -c $(OBJDIR)/sqlcmd_.c
22692273
22702274
$(OBJDIR)/sqlcmd.h: $(OBJDIR)/headers
2275
+
2276
+$(OBJDIR)/sqldiff_.c: $(SRCDIR)/sqldiff.c $(TRANSLATE)
2277
+ $(TRANSLATE) $(SRCDIR)/sqldiff.c >$@
2278
+
2279
+$(OBJDIR)/sqldiff.o: $(OBJDIR)/sqldiff_.c $(OBJDIR)/sqldiff.h $(SRCDIR)/config.h
2280
+ $(XTCC) -o $(OBJDIR)/sqldiff.o -c $(OBJDIR)/sqldiff_.c
2281
+
2282
+$(OBJDIR)/sqldiff.h: $(OBJDIR)/headers
22712283
22722284
$(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(TRANSLATE)
22732285
$(TRANSLATE) $(SRCDIR)/stash.c >$@
22742286
22752287
$(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h
22762288
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -518,10 +518,11 @@
518 $(SRCDIR)/shun.c \
519 $(SRCDIR)/sitemap.c \
520 $(SRCDIR)/skins.c \
521 $(SRCDIR)/smtp.c \
522 $(SRCDIR)/sqlcmd.c \
 
523 $(SRCDIR)/stash.c \
524 $(SRCDIR)/stat.c \
525 $(SRCDIR)/statrep.c \
526 $(SRCDIR)/style.c \
527 $(SRCDIR)/sync.c \
@@ -782,10 +783,11 @@
782 $(OBJDIR)/shun_.c \
783 $(OBJDIR)/sitemap_.c \
784 $(OBJDIR)/skins_.c \
785 $(OBJDIR)/smtp_.c \
786 $(OBJDIR)/sqlcmd_.c \
 
787 $(OBJDIR)/stash_.c \
788 $(OBJDIR)/stat_.c \
789 $(OBJDIR)/statrep_.c \
790 $(OBJDIR)/style_.c \
791 $(OBJDIR)/sync_.c \
@@ -931,10 +933,11 @@
931 $(OBJDIR)/shun.o \
932 $(OBJDIR)/sitemap.o \
933 $(OBJDIR)/skins.o \
934 $(OBJDIR)/smtp.o \
935 $(OBJDIR)/sqlcmd.o \
 
936 $(OBJDIR)/stash.o \
937 $(OBJDIR)/stat.o \
938 $(OBJDIR)/statrep.o \
939 $(OBJDIR)/style.o \
940 $(OBJDIR)/sync.o \
@@ -1284,10 +1287,11 @@
1284 $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
1285 $(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
1286 $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
1287 $(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \
1288 $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h \
 
1289 $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \
1290 $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \
1291 $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \
1292 $(OBJDIR)/style_.c:$(OBJDIR)/style.h \
1293 $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \
@@ -2266,10 +2270,18 @@
2266
2267 $(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h
2268 $(XTCC) -o $(OBJDIR)/sqlcmd.o -c $(OBJDIR)/sqlcmd_.c
2269
2270 $(OBJDIR)/sqlcmd.h: $(OBJDIR)/headers
 
 
 
 
 
 
 
 
2271
2272 $(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(TRANSLATE)
2273 $(TRANSLATE) $(SRCDIR)/stash.c >$@
2274
2275 $(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h
2276
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -518,10 +518,11 @@
518 $(SRCDIR)/shun.c \
519 $(SRCDIR)/sitemap.c \
520 $(SRCDIR)/skins.c \
521 $(SRCDIR)/smtp.c \
522 $(SRCDIR)/sqlcmd.c \
523 $(SRCDIR)/sqldiff.c \
524 $(SRCDIR)/stash.c \
525 $(SRCDIR)/stat.c \
526 $(SRCDIR)/statrep.c \
527 $(SRCDIR)/style.c \
528 $(SRCDIR)/sync.c \
@@ -782,10 +783,11 @@
783 $(OBJDIR)/shun_.c \
784 $(OBJDIR)/sitemap_.c \
785 $(OBJDIR)/skins_.c \
786 $(OBJDIR)/smtp_.c \
787 $(OBJDIR)/sqlcmd_.c \
788 $(OBJDIR)/sqldiff_.c \
789 $(OBJDIR)/stash_.c \
790 $(OBJDIR)/stat_.c \
791 $(OBJDIR)/statrep_.c \
792 $(OBJDIR)/style_.c \
793 $(OBJDIR)/sync_.c \
@@ -931,10 +933,11 @@
933 $(OBJDIR)/shun.o \
934 $(OBJDIR)/sitemap.o \
935 $(OBJDIR)/skins.o \
936 $(OBJDIR)/smtp.o \
937 $(OBJDIR)/sqlcmd.o \
938 $(OBJDIR)/sqldiff.o \
939 $(OBJDIR)/stash.o \
940 $(OBJDIR)/stat.o \
941 $(OBJDIR)/statrep.o \
942 $(OBJDIR)/style.o \
943 $(OBJDIR)/sync.o \
@@ -1284,10 +1287,11 @@
1287 $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \
1288 $(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \
1289 $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \
1290 $(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \
1291 $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h \
1292 $(OBJDIR)/sqldiff_.c:$(OBJDIR)/sqldiff.h \
1293 $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \
1294 $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \
1295 $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \
1296 $(OBJDIR)/style_.c:$(OBJDIR)/style.h \
1297 $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \
@@ -2266,10 +2270,18 @@
2270
2271 $(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h
2272 $(XTCC) -o $(OBJDIR)/sqlcmd.o -c $(OBJDIR)/sqlcmd_.c
2273
2274 $(OBJDIR)/sqlcmd.h: $(OBJDIR)/headers
2275
2276 $(OBJDIR)/sqldiff_.c: $(SRCDIR)/sqldiff.c $(TRANSLATE)
2277 $(TRANSLATE) $(SRCDIR)/sqldiff.c >$@
2278
2279 $(OBJDIR)/sqldiff.o: $(OBJDIR)/sqldiff_.c $(OBJDIR)/sqldiff.h $(SRCDIR)/config.h
2280 $(XTCC) -o $(OBJDIR)/sqldiff.o -c $(OBJDIR)/sqldiff_.c
2281
2282 $(OBJDIR)/sqldiff.h: $(OBJDIR)/headers
2283
2284 $(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(TRANSLATE)
2285 $(TRANSLATE) $(SRCDIR)/stash.c >$@
2286
2287 $(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h
2288
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -476,10 +476,11 @@
476476
"$(OX)\shun_.c" \
477477
"$(OX)\sitemap_.c" \
478478
"$(OX)\skins_.c" \
479479
"$(OX)\smtp_.c" \
480480
"$(OX)\sqlcmd_.c" \
481
+ "$(OX)\sqldiff_.c" \
481482
"$(OX)\stash_.c" \
482483
"$(OX)\stat_.c" \
483484
"$(OX)\statrep_.c" \
484485
"$(OX)\style_.c" \
485486
"$(OX)\sync_.c" \
@@ -742,10 +743,11 @@
742743
"$(OX)\shun$O" \
743744
"$(OX)\sitemap$O" \
744745
"$(OX)\skins$O" \
745746
"$(OX)\smtp$O" \
746747
"$(OX)\sqlcmd$O" \
748
+ "$(OX)\sqldiff$O" \
747749
"$(OX)\sqlite3$O" \
748750
"$(OX)\stash$O" \
749751
"$(OX)\stat$O" \
750752
"$(OX)\statrep$O" \
751753
"$(OX)\style$O" \
@@ -991,10 +993,11 @@
991993
echo "$(OX)\shun.obj" >> $@
992994
echo "$(OX)\sitemap.obj" >> $@
993995
echo "$(OX)\skins.obj" >> $@
994996
echo "$(OX)\smtp.obj" >> $@
995997
echo "$(OX)\sqlcmd.obj" >> $@
998
+ echo "$(OX)\sqldiff.obj" >> $@
996999
echo "$(OX)\sqlite3.obj" >> $@
9971000
echo "$(OX)\stash.obj" >> $@
9981001
echo "$(OX)\stat.obj" >> $@
9991002
echo "$(OX)\statrep.obj" >> $@
10001003
echo "$(OX)\style.obj" >> $@
@@ -1958,10 +1961,16 @@
19581961
"$(OX)\sqlcmd$O" : "$(OX)\sqlcmd_.c" "$(OX)\sqlcmd.h"
19591962
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sqlcmd_.c"
19601963
19611964
"$(OX)\sqlcmd_.c" : "$(SRCDIR)\sqlcmd.c"
19621965
"$(OBJDIR)\translate$E" $** > $@
1966
+
1967
+"$(OX)\sqldiff$O" : "$(OX)\sqldiff_.c" "$(OX)\sqldiff.h"
1968
+ $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sqldiff_.c"
1969
+
1970
+"$(OX)\sqldiff_.c" : "$(SRCDIR)\sqldiff.c"
1971
+ "$(OBJDIR)\translate$E" $** > $@
19631972
19641973
"$(OX)\stash$O" : "$(OX)\stash_.c" "$(OX)\stash.h"
19651974
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\stash_.c"
19661975
19671976
"$(OX)\stash_.c" : "$(SRCDIR)\stash.c"
@@ -2255,10 +2264,11 @@
22552264
"$(OX)\shun_.c":"$(OX)\shun.h" \
22562265
"$(OX)\sitemap_.c":"$(OX)\sitemap.h" \
22572266
"$(OX)\skins_.c":"$(OX)\skins.h" \
22582267
"$(OX)\smtp_.c":"$(OX)\smtp.h" \
22592268
"$(OX)\sqlcmd_.c":"$(OX)\sqlcmd.h" \
2269
+ "$(OX)\sqldiff_.c":"$(OX)\sqldiff.h" \
22602270
"$(OX)\stash_.c":"$(OX)\stash.h" \
22612271
"$(OX)\stat_.c":"$(OX)\stat.h" \
22622272
"$(OX)\statrep_.c":"$(OX)\statrep.h" \
22632273
"$(OX)\style_.c":"$(OX)\style.h" \
22642274
"$(OX)\sync_.c":"$(OX)\sync.h" \
22652275
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -476,10 +476,11 @@
476 "$(OX)\shun_.c" \
477 "$(OX)\sitemap_.c" \
478 "$(OX)\skins_.c" \
479 "$(OX)\smtp_.c" \
480 "$(OX)\sqlcmd_.c" \
 
481 "$(OX)\stash_.c" \
482 "$(OX)\stat_.c" \
483 "$(OX)\statrep_.c" \
484 "$(OX)\style_.c" \
485 "$(OX)\sync_.c" \
@@ -742,10 +743,11 @@
742 "$(OX)\shun$O" \
743 "$(OX)\sitemap$O" \
744 "$(OX)\skins$O" \
745 "$(OX)\smtp$O" \
746 "$(OX)\sqlcmd$O" \
 
747 "$(OX)\sqlite3$O" \
748 "$(OX)\stash$O" \
749 "$(OX)\stat$O" \
750 "$(OX)\statrep$O" \
751 "$(OX)\style$O" \
@@ -991,10 +993,11 @@
991 echo "$(OX)\shun.obj" >> $@
992 echo "$(OX)\sitemap.obj" >> $@
993 echo "$(OX)\skins.obj" >> $@
994 echo "$(OX)\smtp.obj" >> $@
995 echo "$(OX)\sqlcmd.obj" >> $@
 
996 echo "$(OX)\sqlite3.obj" >> $@
997 echo "$(OX)\stash.obj" >> $@
998 echo "$(OX)\stat.obj" >> $@
999 echo "$(OX)\statrep.obj" >> $@
1000 echo "$(OX)\style.obj" >> $@
@@ -1958,10 +1961,16 @@
1958 "$(OX)\sqlcmd$O" : "$(OX)\sqlcmd_.c" "$(OX)\sqlcmd.h"
1959 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sqlcmd_.c"
1960
1961 "$(OX)\sqlcmd_.c" : "$(SRCDIR)\sqlcmd.c"
1962 "$(OBJDIR)\translate$E" $** > $@
 
 
 
 
 
 
1963
1964 "$(OX)\stash$O" : "$(OX)\stash_.c" "$(OX)\stash.h"
1965 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\stash_.c"
1966
1967 "$(OX)\stash_.c" : "$(SRCDIR)\stash.c"
@@ -2255,10 +2264,11 @@
2255 "$(OX)\shun_.c":"$(OX)\shun.h" \
2256 "$(OX)\sitemap_.c":"$(OX)\sitemap.h" \
2257 "$(OX)\skins_.c":"$(OX)\skins.h" \
2258 "$(OX)\smtp_.c":"$(OX)\smtp.h" \
2259 "$(OX)\sqlcmd_.c":"$(OX)\sqlcmd.h" \
 
2260 "$(OX)\stash_.c":"$(OX)\stash.h" \
2261 "$(OX)\stat_.c":"$(OX)\stat.h" \
2262 "$(OX)\statrep_.c":"$(OX)\statrep.h" \
2263 "$(OX)\style_.c":"$(OX)\style.h" \
2264 "$(OX)\sync_.c":"$(OX)\sync.h" \
2265
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -476,10 +476,11 @@
476 "$(OX)\shun_.c" \
477 "$(OX)\sitemap_.c" \
478 "$(OX)\skins_.c" \
479 "$(OX)\smtp_.c" \
480 "$(OX)\sqlcmd_.c" \
481 "$(OX)\sqldiff_.c" \
482 "$(OX)\stash_.c" \
483 "$(OX)\stat_.c" \
484 "$(OX)\statrep_.c" \
485 "$(OX)\style_.c" \
486 "$(OX)\sync_.c" \
@@ -742,10 +743,11 @@
743 "$(OX)\shun$O" \
744 "$(OX)\sitemap$O" \
745 "$(OX)\skins$O" \
746 "$(OX)\smtp$O" \
747 "$(OX)\sqlcmd$O" \
748 "$(OX)\sqldiff$O" \
749 "$(OX)\sqlite3$O" \
750 "$(OX)\stash$O" \
751 "$(OX)\stat$O" \
752 "$(OX)\statrep$O" \
753 "$(OX)\style$O" \
@@ -991,10 +993,11 @@
993 echo "$(OX)\shun.obj" >> $@
994 echo "$(OX)\sitemap.obj" >> $@
995 echo "$(OX)\skins.obj" >> $@
996 echo "$(OX)\smtp.obj" >> $@
997 echo "$(OX)\sqlcmd.obj" >> $@
998 echo "$(OX)\sqldiff.obj" >> $@
999 echo "$(OX)\sqlite3.obj" >> $@
1000 echo "$(OX)\stash.obj" >> $@
1001 echo "$(OX)\stat.obj" >> $@
1002 echo "$(OX)\statrep.obj" >> $@
1003 echo "$(OX)\style.obj" >> $@
@@ -1958,10 +1961,16 @@
1961 "$(OX)\sqlcmd$O" : "$(OX)\sqlcmd_.c" "$(OX)\sqlcmd.h"
1962 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sqlcmd_.c"
1963
1964 "$(OX)\sqlcmd_.c" : "$(SRCDIR)\sqlcmd.c"
1965 "$(OBJDIR)\translate$E" $** > $@
1966
1967 "$(OX)\sqldiff$O" : "$(OX)\sqldiff_.c" "$(OX)\sqldiff.h"
1968 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\sqldiff_.c"
1969
1970 "$(OX)\sqldiff_.c" : "$(SRCDIR)\sqldiff.c"
1971 "$(OBJDIR)\translate$E" $** > $@
1972
1973 "$(OX)\stash$O" : "$(OX)\stash_.c" "$(OX)\stash.h"
1974 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\stash_.c"
1975
1976 "$(OX)\stash_.c" : "$(SRCDIR)\stash.c"
@@ -2255,10 +2264,11 @@
2264 "$(OX)\shun_.c":"$(OX)\shun.h" \
2265 "$(OX)\sitemap_.c":"$(OX)\sitemap.h" \
2266 "$(OX)\skins_.c":"$(OX)\skins.h" \
2267 "$(OX)\smtp_.c":"$(OX)\smtp.h" \
2268 "$(OX)\sqlcmd_.c":"$(OX)\sqlcmd.h" \
2269 "$(OX)\sqldiff_.c":"$(OX)\sqldiff.h" \
2270 "$(OX)\stash_.c":"$(OX)\stash.h" \
2271 "$(OX)\stat_.c":"$(OX)\stat.h" \
2272 "$(OX)\statrep_.c":"$(OX)\statrep.h" \
2273 "$(OX)\style_.c":"$(OX)\style.h" \
2274 "$(OX)\sync_.c":"$(OX)\sync.h" \
2275

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button