Add the "changeset" command-line tool for analyzing and manipulating
changesets in files on disk. Add the ".session" command to the command-line tool. FossilOrigin-Name: 31addb627fdbaeb908e0611ad82f6db7537428ea
This commit is contained in:
commit
5f77b89ecd
412
ext/session/changeset.c
Normal file
412
ext/session/changeset.c
Normal file
@ -0,0 +1,412 @@
|
||||
/*
|
||||
** 2014-08-18
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
** This file contains code to implement the "changeset" command line
|
||||
** utility for displaying and transforming changesets generated by
|
||||
** the Sessions extension.
|
||||
*/
|
||||
#include "sqlite3.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
|
||||
|
||||
/*
|
||||
** Show a usage message on stderr then quit.
|
||||
*/
|
||||
static void usage(const char *argv0){
|
||||
fprintf(stderr, "Usage: %s FILENAME COMMAND ...\n", argv0);
|
||||
fprintf(stderr,
|
||||
"COMMANDs:\n"
|
||||
" apply DB Apply the changeset to database file DB\n"
|
||||
" concat FILE2 OUT Concatenate FILENAME and FILE2 into OUT\n"
|
||||
" dump Show the complete content of the changeset\n"
|
||||
" invert OUT Write an inverted changeset into file OUT\n"
|
||||
" sql Give a pseudo-SQL rendering of the changeset\n"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the content of a disk file into an in-memory buffer
|
||||
*/
|
||||
static void readFile(const char *zFilename, int *pSz, void **ppBuf){
|
||||
FILE *f;
|
||||
int sz;
|
||||
void *pBuf;
|
||||
f = fopen(zFilename, "rb");
|
||||
if( f==0 ){
|
||||
fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename);
|
||||
exit(1);
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
sz = (int)ftell(f);
|
||||
rewind(f);
|
||||
pBuf = sqlite3_malloc( sz ? sz : 1 );
|
||||
if( pBuf==0 ){
|
||||
fprintf(stderr, "cannot allocate %d to hold content of \"%s\"\n",
|
||||
sz, zFilename);
|
||||
exit(1);
|
||||
}
|
||||
if( sz>0 ){
|
||||
if( fread(pBuf, sz, 1, f)!=1 ){
|
||||
fprintf(stderr, "cannot read all %d bytes of \"%s\"\n", sz, zFilename);
|
||||
exit(1);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
*pSz = sz;
|
||||
*ppBuf = pBuf;
|
||||
}
|
||||
|
||||
/* Array for converting from half-bytes (nybbles) into ASCII hex
|
||||
** digits. */
|
||||
static const char hexdigits[] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
||||
};
|
||||
|
||||
/*
|
||||
** Render an sqlite3_value as an SQL string.
|
||||
*/
|
||||
static void renderValue(sqlite3_value *pVal){
|
||||
switch( sqlite3_value_type(pVal) ){
|
||||
case SQLITE_FLOAT: {
|
||||
double r1;
|
||||
char zBuf[50];
|
||||
r1 = sqlite3_value_double(pVal);
|
||||
sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
|
||||
printf("%s", zBuf);
|
||||
break;
|
||||
}
|
||||
case SQLITE_INTEGER: {
|
||||
printf("%lld", sqlite3_value_int64(pVal));
|
||||
break;
|
||||
}
|
||||
case SQLITE_BLOB: {
|
||||
char const *zBlob = sqlite3_value_blob(pVal);
|
||||
int nBlob = sqlite3_value_bytes(pVal);
|
||||
int i;
|
||||
printf("x'");
|
||||
for(i=0; i<nBlob; i++){
|
||||
putchar(hexdigits[(zBlob[i]>>4)&0x0F]);
|
||||
putchar(hexdigits[(zBlob[i])&0x0F]);
|
||||
}
|
||||
putchar('\'');
|
||||
break;
|
||||
}
|
||||
case SQLITE_TEXT: {
|
||||
const unsigned char *zArg = sqlite3_value_text(pVal);
|
||||
putchar('\'');
|
||||
while( zArg[0] ){
|
||||
putchar(zArg[0]);
|
||||
if( zArg[0]=='\'' ) putchar(zArg[0]);
|
||||
zArg++;
|
||||
}
|
||||
putchar('\'');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assert( sqlite3_value_type(pVal)==SQLITE_NULL );
|
||||
printf("NULL");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Number of conflicts seen
|
||||
*/
|
||||
static int nConflict = 0;
|
||||
|
||||
/*
|
||||
** The conflict callback
|
||||
*/
|
||||
static int conflictCallback(
|
||||
void *pCtx,
|
||||
int eConflict,
|
||||
sqlite3_changeset_iter *pIter
|
||||
){
|
||||
int op, bIndirect, nCol, i;
|
||||
const char *zTab;
|
||||
unsigned char *abPK;
|
||||
const char *zType = "";
|
||||
const char *zOp = "";
|
||||
const char *zSep = " ";
|
||||
|
||||
nConflict++;
|
||||
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
|
||||
sqlite3changeset_pk(pIter, &abPK, 0);
|
||||
switch( eConflict ){
|
||||
case SQLITE_CHANGESET_DATA: zType = "DATA"; break;
|
||||
case SQLITE_CHANGESET_NOTFOUND: zType = "NOTFOUND"; break;
|
||||
case SQLITE_CHANGESET_CONFLICT: zType = "PRIMARY KEY"; break;
|
||||
case SQLITE_CHANGESET_FOREIGN_KEY: zType = "FOREIGN KEY"; break;
|
||||
case SQLITE_CHANGESET_CONSTRAINT: zType = "CONSTRAINT"; break;
|
||||
}
|
||||
switch( op ){
|
||||
case SQLITE_UPDATE: zOp = "UPDATE of"; break;
|
||||
case SQLITE_INSERT: zOp = "INSERT into"; break;
|
||||
case SQLITE_DELETE: zOp = "DELETE from"; break;
|
||||
}
|
||||
printf("%s conflict on %s table %s with primary key", zType, zOp, zTab);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
if( abPK[i]==0 ) continue;
|
||||
printf("%s", zSep);
|
||||
if( op==SQLITE_INSERT ){
|
||||
sqlite3changeset_new(pIter, i, &pVal);
|
||||
}else{
|
||||
sqlite3changeset_old(pIter, i, &pVal);
|
||||
}
|
||||
renderValue(pVal);
|
||||
zSep = ",";
|
||||
}
|
||||
printf("\n");
|
||||
return SQLITE_CHANGESET_OMIT;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv){
|
||||
int sz, rc;
|
||||
void *pBuf = 0;
|
||||
if( argc<3 ) usage(argv[0]);
|
||||
readFile(argv[1], &sz, &pBuf);
|
||||
|
||||
/* changeset FILENAME apply DB
|
||||
** Apply the changeset in FILENAME to the database file DB
|
||||
*/
|
||||
if( strcmp(argv[2],"apply")==0 ){
|
||||
sqlite3 *db;
|
||||
if( argc!=4 ) usage(argv[0]);
|
||||
rc = sqlite3_open(argv[3], &db);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "unable to open database file \"%s\": %s\n",
|
||||
argv[3], sqlite3_errmsg(db));
|
||||
sqlite3_close(db);
|
||||
exit(1);
|
||||
}
|
||||
sqlite3_exec(db, "BEGIN", 0, 0, 0);
|
||||
nConflict = 0;
|
||||
rc = sqlite3changeset_apply(db, sz, pBuf, 0, conflictCallback, 0);
|
||||
if( rc ){
|
||||
fprintf(stderr, "sqlite3changeset_apply() returned %d\n", rc);
|
||||
}
|
||||
if( nConflict ){
|
||||
fprintf(stderr, "%d conflicts - no changes applied\n", nConflict);
|
||||
sqlite3_exec(db, "ROLLBACK", 0, 0, 0);
|
||||
}else if( rc ){
|
||||
fprintf(stderr, "sqlite3changeset_apply() returns %d "
|
||||
"- no changes applied\n", rc);
|
||||
sqlite3_exec(db, "ROLLBACK", 0, 0, 0);
|
||||
}else{
|
||||
sqlite3_exec(db, "COMMIT", 0, 0, 0);
|
||||
}
|
||||
sqlite3_close(db);
|
||||
}else
|
||||
|
||||
/* changeset FILENAME concat FILE2 OUT
|
||||
** Add changeset FILE2 onto the end of the changeset in FILENAME
|
||||
** and write the result into OUT.
|
||||
*/
|
||||
if( strcmp(argv[2],"concat")==0 ){
|
||||
int szB;
|
||||
void *pB;
|
||||
int szOut;
|
||||
void *pOutBuf;
|
||||
FILE *out;
|
||||
const char *zOut = argv[4];
|
||||
if( argc!=5 ) usage(argv[0]);
|
||||
out = fopen(zOut, "wb");
|
||||
if( out==0 ){
|
||||
fprintf(stderr, "cannot open \"%s\" for writing\n", zOut);
|
||||
exit(1);
|
||||
}
|
||||
readFile(argv[3], &szB, &pB);
|
||||
sqlite3changeset_concat(sz, pBuf, szB, pB, &szOut, &pOutBuf);
|
||||
if( fwrite(pOutBuf, szOut, 1, out)!=1 ){
|
||||
fprintf(stderr, "unable to write all %d bytes of output to \"%s\"\n",
|
||||
szOut, zOut);
|
||||
}
|
||||
fclose(out);
|
||||
sqlite3_free(pOutBuf);
|
||||
sqlite3_free(pB);
|
||||
}else
|
||||
|
||||
/* changeset FILENAME dump
|
||||
** Show the complete content of the changeset in FILENAME
|
||||
*/
|
||||
if( strcmp(argv[2],"dump")==0 ){
|
||||
int cnt = 0;
|
||||
int i;
|
||||
sqlite3_changeset_iter *pIter;
|
||||
rc = sqlite3changeset_start(&pIter, sz, pBuf);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3changeset_start() returns %d\n", rc);
|
||||
exit(1);
|
||||
}
|
||||
while( sqlite3changeset_next(pIter)==SQLITE_ROW ){
|
||||
int op, bIndirect, nCol;
|
||||
const char *zTab;
|
||||
unsigned char *abPK;
|
||||
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
|
||||
cnt++;
|
||||
printf("%d: %s table=[%s] indirect=%d nColumn=%d\n",
|
||||
cnt, op==SQLITE_INSERT ? "INSERT" :
|
||||
op==SQLITE_UPDATE ? "UPDATE" : "DELETE",
|
||||
zTab, bIndirect, nCol);
|
||||
sqlite3changeset_pk(pIter, &abPK, 0);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
pVal = 0;
|
||||
sqlite3changeset_old(pIter, i, &pVal);
|
||||
if( pVal ){
|
||||
printf(" old[%d]%s = ", i, abPK[i] ? "pk" : " ");
|
||||
renderValue(pVal);
|
||||
printf("\n");
|
||||
}
|
||||
pVal = 0;
|
||||
sqlite3changeset_new(pIter, i, &pVal);
|
||||
if( pVal ){
|
||||
printf(" new[%d]%s = ", i, abPK[i] ? "pk" : " ");
|
||||
renderValue(pVal);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3changeset_finalize(pIter);
|
||||
}else
|
||||
|
||||
/* changeset FILENAME invert OUT
|
||||
** Invert the changes in FILENAME and writes the result on OUT
|
||||
*/
|
||||
if( strcmp(argv[2],"invert")==0 ){
|
||||
FILE *out;
|
||||
int szOut = 0;
|
||||
void *pOutBuf = 0;
|
||||
const char *zOut = argv[3];
|
||||
if( argc!=4 ) usage(argv[0]);
|
||||
out = fopen(zOut, "wb");
|
||||
if( out==0 ){
|
||||
fprintf(stderr, "cannot open \"%s\" for writing\n", zOut);
|
||||
exit(1);
|
||||
}
|
||||
sqlite3changeset_invert(sz, pBuf, &szOut, &pOutBuf);
|
||||
if( fwrite(pOutBuf, szOut, 1, out)!=1 ){
|
||||
fprintf(stderr, "unable to write all %d bytes of output to \"%s\"\n",
|
||||
szOut, zOut);
|
||||
}
|
||||
fclose(out);
|
||||
sqlite3_free(pOutBuf);
|
||||
}else
|
||||
|
||||
/* changeset FILE sql
|
||||
** Show the content of the changeset as pseudo-SQL
|
||||
*/
|
||||
if( strcmp(argv[2],"sql")==0 ){
|
||||
int cnt = 0;
|
||||
char *zPrevTab = 0;
|
||||
char *zSQLTabName = 0;
|
||||
sqlite3_changeset_iter *pIter = 0;
|
||||
rc = sqlite3changeset_start(&pIter, sz, pBuf);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3changeset_start() returns %d\n", rc);
|
||||
exit(1);
|
||||
}
|
||||
printf("BEGIN;\n");
|
||||
while( sqlite3changeset_next(pIter)==SQLITE_ROW ){
|
||||
int op, bIndirect, nCol;
|
||||
const char *zTab;
|
||||
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
|
||||
cnt++;
|
||||
if( zPrevTab==0 || strcmp(zPrevTab,zTab)!=0 ){
|
||||
sqlite3_free(zPrevTab);
|
||||
sqlite3_free(zSQLTabName);
|
||||
zPrevTab = sqlite3_mprintf("%s", zTab);
|
||||
if( !isalnum(zTab[0]) || sqlite3_strglob("*[^a-zA-Z0-9]*",zTab)==0 ){
|
||||
zSQLTabName = sqlite3_mprintf("\"%w\"", zTab);
|
||||
}else{
|
||||
zSQLTabName = sqlite3_mprintf("%s", zTab);
|
||||
}
|
||||
printf("/****** Changes for table %s ***************/\n", zSQLTabName);
|
||||
}
|
||||
switch( op ){
|
||||
case SQLITE_DELETE: {
|
||||
unsigned char *abPK;
|
||||
int i;
|
||||
const char *zSep = " ";
|
||||
sqlite3changeset_pk(pIter, &abPK, 0);
|
||||
printf("/* %d */ DELETE FROM %s WHERE", cnt, zSQLTabName);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
if( abPK[i]==0 ) continue;
|
||||
printf("%sc%d=", zSep, i+1);
|
||||
zSep = " AND ";
|
||||
sqlite3changeset_old(pIter, i, &pVal);
|
||||
renderValue(pVal);
|
||||
}
|
||||
printf(";\n");
|
||||
break;
|
||||
}
|
||||
case SQLITE_UPDATE: {
|
||||
unsigned char *abPK;
|
||||
int i;
|
||||
const char *zSep = " ";
|
||||
sqlite3changeset_pk(pIter, &abPK, 0);
|
||||
printf("/* %d */ UPDATE %s SET", cnt, zSQLTabName);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal = 0;
|
||||
sqlite3changeset_new(pIter, i, &pVal);
|
||||
if( pVal ){
|
||||
printf("%sc%d=", zSep, i+1);
|
||||
zSep = ", ";
|
||||
renderValue(pVal);
|
||||
}
|
||||
}
|
||||
printf(" WHERE");
|
||||
zSep = " ";
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
if( abPK[i]==0 ) continue;
|
||||
printf("%sc%d=", zSep, i+1);
|
||||
zSep = " AND ";
|
||||
sqlite3changeset_old(pIter, i, &pVal);
|
||||
renderValue(pVal);
|
||||
}
|
||||
printf(";\n");
|
||||
break;
|
||||
}
|
||||
case SQLITE_INSERT: {
|
||||
int i;
|
||||
printf("/* %d */ INSERT INTO %s VALUES", cnt, zSQLTabName);
|
||||
for(i=0; i<nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
printf("%c", i==0 ? '(' : ',');
|
||||
sqlite3changeset_new(pIter, i, &pVal);
|
||||
renderValue(pVal);
|
||||
}
|
||||
printf(");\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("COMMIT;\n");
|
||||
sqlite3changeset_finalize(pIter);
|
||||
sqlite3_free(zPrevTab);
|
||||
sqlite3_free(zSQLTabName);
|
||||
}else
|
||||
|
||||
/* If nothing else matches, show the usage comment */
|
||||
usage(argv[0]);
|
||||
sqlite3_free(pBuf);
|
||||
return 0;
|
||||
}
|
9
main.mk
9
main.mk
@ -72,6 +72,8 @@ LIBOBJ+= vdbe.o parse.o \
|
||||
vdbeapi.o vdbeaux.o vdbeblob.o vdbemem.o vdbesort.o \
|
||||
vdbetrace.o wal.o walker.o where.o utf.o vtab.o
|
||||
|
||||
LIBOBJ += sqlite3session.o
|
||||
|
||||
|
||||
|
||||
# All of the source code files.
|
||||
@ -563,6 +565,9 @@ fts3_write.o: $(TOP)/ext/fts3/fts3_write.c $(HDR) $(EXTHDR)
|
||||
rtree.o: $(TOP)/ext/rtree/rtree.c $(HDR) $(EXTHDR)
|
||||
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/rtree/rtree.c
|
||||
|
||||
sqlite3session.o: $(TOP)/ext/session/sqlite3session.c $(HDR) $(EXTHDR)
|
||||
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/session/sqlite3session.c
|
||||
|
||||
|
||||
# Rules for building test programs and for running tests
|
||||
#
|
||||
@ -655,6 +660,10 @@ showwal$(EXE): $(TOP)/tool/showwal.c sqlite3.o
|
||||
$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o showwal$(EXE) \
|
||||
$(TOP)/tool/showwal.c sqlite3.o $(THREADLIB)
|
||||
|
||||
changeset$(EXE): $(TOP)/ext/session/changeset.c sqlite3.o
|
||||
$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o changeset$(EXE) \
|
||||
$(TOP)/ext/session/changeset.c sqlite3.o $(THREADLIB)
|
||||
|
||||
fts3view$(EXE): $(TOP)/ext/fts3/tool/fts3view.c sqlite3.o
|
||||
$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o fts3view$(EXE) \
|
||||
$(TOP)/ext/fts3/tool/fts3view.c sqlite3.o $(THREADLIB)
|
||||
|
18
manifest
18
manifest
@ -1,5 +1,5 @@
|
||||
C Add\smiscellaneous\stest\scases\sto\simprove\scoverage\sof\ssessions\smodule.
|
||||
D 2014-08-18T16:03:46.849
|
||||
C Add\sthe\s"changeset"\scommand-line\stool\sfor\sanalyzing\sand\smanipulating\nchangesets\sin\sfiles\son\sdisk.\s\sAdd\sthe\s".session"\scommand\sto\sthe\scommand-line\ntool.
|
||||
D 2014-08-18T20:08:25.748
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in 639859a6f81bd15921ccd56ddbd6dfd335278377
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
@ -143,6 +143,7 @@ F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea
|
||||
F ext/rtree/sqlite3rtree.h 83349d519fe5f518b3ea025d18dd1fe51b1684bd
|
||||
F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
|
||||
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
|
||||
F ext/session/changeset.c 9be709cea346d65c6d0cc8bf03569956af125462
|
||||
F ext/session/session1.test 3733d71eb9a99b14d51fa5219d5b8a82407c3be8
|
||||
F ext/session/session2.test 99ca0da7ddb617d42bafd83adccf99f18ae0384b
|
||||
F ext/session/session3.test a7a9ce59b8d1e49e2cc23d81421ac485be0eea01
|
||||
@ -161,7 +162,7 @@ F ext/session/test_session.c a252fb669d3a1b3552ee7b87fe610debc0afeb7b
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
|
||||
F main.mk 152593de5d19eeab0fdaacfdedd5eafdfdd83d5a
|
||||
F main.mk 95de2e32fbe62b10e28bd7a131e2b003b923a8b3
|
||||
F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
|
||||
F mkopcodeh.awk c6b3fa301db6ef7ac916b14c60868aeaec1337b5
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
@ -238,7 +239,7 @@ F src/random.c d10c1f85b6709ca97278428fd5db5bbb9c74eece
|
||||
F src/resolve.c 0ea356d32a5e884add23d1b9b4e8736681dd5697
|
||||
F src/rowset.c a9c9aae3234b44a6d7c6f5a3cadf90dce1e627be
|
||||
F src/select.c ea48e891406ccdf748f3eb02893e056d134a0fea
|
||||
F src/shell.c 41df1103617331e023301d943977f8c06bd1486f
|
||||
F src/shell.c 1761e117dd58e2383a2d30319c49d0fca00f8bf0
|
||||
F src/sqlite.h.in 021a1f5c50e83060675d994a6014fd409e611d9e
|
||||
F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
|
||||
F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc
|
||||
@ -1202,7 +1203,8 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
|
||||
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
|
||||
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
|
||||
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
|
||||
P 419d286a2fc465f6e0f9662909d0cc52a18eefa4
|
||||
R 317ae23cec852c0c0a1e8f934088d0af
|
||||
U dan
|
||||
Z 05b5d773ef95bfd2114231b8fc3c0f06
|
||||
P 0fac6cfffe628ea02c78ebad065307309ec9eaa1 7b12f1f9c012f33d376242920583807b014b3287
|
||||
R 7a7a7eda9e9e796cac11d47296a11e68
|
||||
T +closed 7b12f1f9c012f33d376242920583807b014b3287
|
||||
U drh
|
||||
Z bd91644aa3e055b27f4fc846b7cc054f
|
||||
|
@ -1 +1 @@
|
||||
0fac6cfffe628ea02c78ebad065307309ec9eaa1
|
||||
31addb627fdbaeb908e0611ad82f6db7537428ea
|
280
src/shell.c
280
src/shell.c
@ -432,6 +432,19 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
|
||||
return zResult;
|
||||
}
|
||||
|
||||
#if defined(SQLITE_ENABLE_SESSION)
|
||||
/*
|
||||
** State information for a single open session
|
||||
*/
|
||||
typedef struct OpenSession OpenSession;
|
||||
struct OpenSession {
|
||||
char *zName; /* Symbolic name for this session */
|
||||
int nFilter; /* Number of xFilter rejection GLOB patterns */
|
||||
char **azFilter; /* Array of xFilter rejection GLOB patterns */
|
||||
sqlite3_session *p; /* The open session */
|
||||
};
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Shell output mode information from before ".explain on",
|
||||
** saved so that it can be restored by ".explain off"
|
||||
@ -479,6 +492,10 @@ struct ShellState {
|
||||
int *aiIndent; /* Array of indents used in MODE_Explain */
|
||||
int nIndent; /* Size of array aiIndent[] */
|
||||
int iIndent; /* Index of current op in aiIndent[] */
|
||||
#if defined(SQLITE_ENABLE_SESSION)
|
||||
int nSession; /* Number of active sessions */
|
||||
OpenSession aSession[4]; /* Array of sessions. [0] is in focus. */
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
@ -1636,6 +1653,9 @@ static char zHelp[] =
|
||||
" LIKE pattern TABLE.\n"
|
||||
".separator STRING ?NL? Change separator used by output mode and .import\n"
|
||||
" NL is the end-of-line mark for CSV\n"
|
||||
#if defined(SQLITE_ENABLE_SESSION)
|
||||
".session CMD ... Create or control sessions\n"
|
||||
#endif
|
||||
".shell CMD ARGS... Run CMD ARGS... in a system shell\n"
|
||||
".show Show the current values for various settings\n"
|
||||
".stats on|off Turn stats on or off\n"
|
||||
@ -1651,6 +1671,30 @@ static char zHelp[] =
|
||||
" Negative values right-justify\n"
|
||||
;
|
||||
|
||||
#if defined(SQLITE_ENABLE_SESSION)
|
||||
/*
|
||||
** Print help information for the ".sessions" command
|
||||
*/
|
||||
void session_help(ShellState *p){
|
||||
fprintf(p->out,
|
||||
".session ?NAME? SUBCOMMAND ?ARGS...?\n"
|
||||
"If ?NAME? is omitted, the first defined session is used.\n"
|
||||
"Subcommands:\n"
|
||||
" attach TABLE Attach TABLE\n"
|
||||
" changeset FILE Write a changeset into FILE\n"
|
||||
" close Close one session\n"
|
||||
" enable ?BOOLEAN? Set or query the enable bit\n"
|
||||
" filter GLOB... Reject tables matching GLOBs\n"
|
||||
" indirect ?BOOLEAN? Mark or query the indirect status\n"
|
||||
" isempty Query whether the session is empty\n"
|
||||
" list List currently open session names\n"
|
||||
" open DB NAME Open a new session on DB\n"
|
||||
" patchset FILE Write a patchset into FILE\n"
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* Forward reference */
|
||||
static int process_input(ShellState *p, FILE *in);
|
||||
/*
|
||||
@ -1714,6 +1758,51 @@ static void writefileFunc(
|
||||
sqlite3_result_int64(context, rc);
|
||||
}
|
||||
|
||||
#if defined(SQLITE_ENABLE_SESSION)
|
||||
/*
|
||||
** Close a single OpenSession object and release all of its associated
|
||||
** resources.
|
||||
*/
|
||||
static void session_close(OpenSession *pSession){
|
||||
int i;
|
||||
sqlite3session_delete(pSession->p);
|
||||
sqlite3_free(pSession->zName);
|
||||
for(i=0; i<pSession->nFilter; i++){
|
||||
sqlite3_free(pSession->azFilter[i]);
|
||||
}
|
||||
sqlite3_free(pSession->azFilter);
|
||||
memset(pSession, 0, sizeof(OpenSession));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Close all OpenSession objects and release all assocaited resources.
|
||||
*/
|
||||
static void session_close_all(ShellState *p){
|
||||
#if defined(SQLITE_ENABLE_SESSION)
|
||||
int i;
|
||||
for(i=0; i<p->nSession; i++){
|
||||
session_close(&p->aSession[i]);
|
||||
}
|
||||
p->nSession = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of the xFilter function for an open session. Omit
|
||||
** any tables named by ".session filter" but let all other table through.
|
||||
*/
|
||||
#if defined(SQLITE_ENABLE_SESSION)
|
||||
static int session_filter(void *pCtx, const char *zTab){
|
||||
OpenSession *pSession = (OpenSession*)pCtx;
|
||||
int i;
|
||||
for(i=0; i<pSession->nFilter; i++){
|
||||
if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Make sure the database is open. If it is not, then open it. If
|
||||
** the database fails to open, print an error message and exit.
|
||||
@ -2865,6 +2954,7 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
}
|
||||
open_db(p, 1);
|
||||
if( p->db!=0 ){
|
||||
session_close_all(p);
|
||||
sqlite3_close(savedDb);
|
||||
sqlite3_free(p->zFreeOnClose);
|
||||
p->zFreeOnClose = zNewFilename;
|
||||
@ -3086,6 +3176,195 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
}
|
||||
}else
|
||||
|
||||
#if defined(SQLITE_ENABLE_SESSION)
|
||||
if( c=='s' && strncmp(azArg[0],"session",n)==0 && n>=3 ){
|
||||
OpenSession *pSession = &p->aSession[0];
|
||||
char **azCmd = &azArg[1];
|
||||
int iSes = 0;
|
||||
int nCmd = nArg - 1;
|
||||
int i;
|
||||
if( nArg<=1 ) goto session_syntax_error;
|
||||
open_db(p, 0);
|
||||
if( nArg>=3 ){
|
||||
for(iSes=0; iSes<p->nSession; iSes++){
|
||||
if( strcmp(p->aSession[iSes].zName, azArg[1])==0 ) break;
|
||||
}
|
||||
if( iSes<p->nSession ){
|
||||
pSession = &p->aSession[iSes];
|
||||
azCmd++;
|
||||
nCmd--;
|
||||
}else{
|
||||
pSession = &p->aSession[0];
|
||||
iSes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* .session attach TABLE
|
||||
** Invoke the sqlite3session_attach() interface to attach a particular
|
||||
** table so that it is never filtered.
|
||||
*/
|
||||
if( strcmp(azCmd[0],"attach")==0 ){
|
||||
if( nCmd!=2 ) goto session_syntax_error;
|
||||
if( pSession->p==0 ){
|
||||
session_not_open:
|
||||
fprintf(stderr, "ERROR: No sessions are open\n");
|
||||
}else{
|
||||
rc = sqlite3session_attach(pSession->p, azCmd[1]);
|
||||
if( rc ){
|
||||
fprintf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc);
|
||||
rc = 0;
|
||||
}
|
||||
}
|
||||
}else
|
||||
|
||||
/* .session changeset FILE
|
||||
** .session patchset FILE
|
||||
** Write a changeset or patchset into a file. The file is overwritten.
|
||||
*/
|
||||
if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
|
||||
FILE *out = 0;
|
||||
if( nCmd!=2 ) goto session_syntax_error;
|
||||
if( pSession->p==0 ) goto session_not_open;
|
||||
out = fopen(azCmd[1], "wb");
|
||||
if( out==0 ){
|
||||
fprintf(stderr, "ERROR: cannot open \"%s\" for writing\n", azCmd[1]);
|
||||
}else{
|
||||
int szChng;
|
||||
void *pChng;
|
||||
if( azCmd[0][0]=='c' ){
|
||||
sqlite3session_changeset(pSession->p, &szChng, &pChng);
|
||||
}else{
|
||||
sqlite3session_patchset(pSession->p, &szChng, &pChng);
|
||||
}
|
||||
if( pChng
|
||||
&& fwrite(pChng, szChng, 1, out)!=1 ){
|
||||
fprintf(stderr, "ERROR: Failed to write entire %d-byte output\n",
|
||||
szChng);
|
||||
}
|
||||
sqlite3_free(pChng);
|
||||
fclose(out);
|
||||
}
|
||||
}else
|
||||
|
||||
/* .session close
|
||||
** Close the identified session
|
||||
*/
|
||||
if( strcmp(azCmd[0], "close")==0 ){
|
||||
if( nCmd!=1 ) goto session_syntax_error;
|
||||
if( p->nSession ){
|
||||
session_close(pSession);
|
||||
p->aSession[iSes] = p->aSession[--p->nSession];
|
||||
}
|
||||
}else
|
||||
|
||||
/* .session enable ?BOOLEAN?
|
||||
** Query or set the enable flag
|
||||
*/
|
||||
if( strcmp(azCmd[0], "enable")==0 ){
|
||||
int ii;
|
||||
if( nCmd>2 ) goto session_syntax_error;
|
||||
ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
|
||||
if( p->nSession ){
|
||||
ii = sqlite3session_enable(pSession->p, ii);
|
||||
fprintf(p->out, "session %s enable flag = %d\n", pSession->zName, ii);
|
||||
}
|
||||
}else
|
||||
|
||||
/* .session filter GLOB ....
|
||||
** Set a list of GLOB patterns of table names to be excluded.
|
||||
*/
|
||||
if( strcmp(azCmd[0], "filter")==0 ){
|
||||
int ii, nByte;
|
||||
if( nCmd<2 ) goto session_syntax_error;
|
||||
if( p->nSession ){
|
||||
for(ii=0; ii<pSession->nFilter; ii++){
|
||||
sqlite3_free(pSession->azFilter[ii]);
|
||||
}
|
||||
sqlite3_free(pSession->azFilter);
|
||||
nByte = sizeof(pSession->azFilter[0])*(nCmd-1);
|
||||
pSession->azFilter = sqlite3_malloc( nByte );
|
||||
if( pSession->azFilter==0 ){
|
||||
fprintf(stderr, "Error: out or memory\n");
|
||||
exit(1);
|
||||
}
|
||||
for(ii=1; ii<nCmd; ii++){
|
||||
pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]);
|
||||
}
|
||||
pSession->nFilter = ii-1;
|
||||
}
|
||||
}else
|
||||
|
||||
/* .session indirect ?BOOLEAN?
|
||||
** Query or set the indirect flag
|
||||
*/
|
||||
if( strcmp(azCmd[0], "indirect")==0 ){
|
||||
int ii;
|
||||
if( nCmd>2 ) goto session_syntax_error;
|
||||
ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
|
||||
if( p->nSession ){
|
||||
ii = sqlite3session_indirect(pSession->p, ii);
|
||||
fprintf(p->out, "session %s indirect flag = %d\n", pSession->zName,ii);
|
||||
}
|
||||
}else
|
||||
|
||||
/* .session isempty
|
||||
** Determine if the session is empty
|
||||
*/
|
||||
if( strcmp(azCmd[0], "isempty")==0 ){
|
||||
int ii;
|
||||
if( nCmd!=1 ) goto session_syntax_error;
|
||||
if( p->nSession ){
|
||||
ii = sqlite3session_isempty(pSession->p);
|
||||
fprintf(p->out, "session %s isempty flag = %d\n", pSession->zName, ii);
|
||||
}
|
||||
}else
|
||||
|
||||
/* .session list
|
||||
** List all currently open sessions
|
||||
*/
|
||||
if( strcmp(azCmd[0],"list")==0 ){
|
||||
for(i=0; i<p->nSession; i++){
|
||||
fprintf(p->out, "%d %s\n", i, p->aSession[i].zName);
|
||||
}
|
||||
}else
|
||||
|
||||
/* .session open DB NAME
|
||||
** Open a new session called NAME on the attached database DB.
|
||||
** DB is normally "main".
|
||||
*/
|
||||
if( strcmp(azCmd[0],"open")==0 ){
|
||||
char *zName;
|
||||
if( nCmd!=3 ) goto session_syntax_error;
|
||||
zName = azCmd[2];
|
||||
if( zName[0]==0 ) goto session_syntax_error;
|
||||
for(i=0; i<p->nSession; i++){
|
||||
if( strcmp(p->aSession[i].zName,zName)==0 ){
|
||||
fprintf(stderr, "Session \"%s\" already exists\n", zName);
|
||||
goto meta_command_exit;
|
||||
}
|
||||
}
|
||||
if( p->nSession>=ArraySize(p->aSession) ){
|
||||
fprintf(stderr, "Maximum of %d sessions\n", ArraySize(p->aSession));
|
||||
goto meta_command_exit;
|
||||
}
|
||||
pSession = &p->aSession[p->nSession];
|
||||
rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
|
||||
if( rc ){
|
||||
fprintf(stderr, "Cannot open session: error code=%d\n", rc);
|
||||
rc = 0;
|
||||
goto meta_command_exit;
|
||||
}
|
||||
pSession->nFilter = 0;
|
||||
sqlite3session_table_filter(pSession->p, session_filter, pSession);
|
||||
p->nSession++;
|
||||
pSession->zName = sqlite3_mprintf("%s", zName);
|
||||
}else
|
||||
/* If no command name matches, show a syntax error */
|
||||
session_syntax_error:
|
||||
session_help(p);
|
||||
}else
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
/* Undocumented commands for internal testing. Subject to change
|
||||
** without notice. */
|
||||
@ -4134,6 +4413,7 @@ int main(int argc, char **argv){
|
||||
}
|
||||
set_table_name(&data, 0);
|
||||
if( data.db ){
|
||||
session_close_all(&data);
|
||||
sqlite3_close(data.db);
|
||||
}
|
||||
sqlite3_free(data.zFreeOnClose);
|
||||
|
Loading…
x
Reference in New Issue
Block a user