Add the dbdump.c extension that implements functionality similar to the
".dump" command of the CLI, though in a separate library. FossilOrigin-Name: 2b9980a292d7f0d995b77d9b3fb217bb2206f6d452e036ac502fa06561af4c12
This commit is contained in:
commit
29df8d1fc2
@ -1154,6 +1154,10 @@ sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl
|
||||
sqlite3_analyzer$(TEXE): sqlite3_analyzer.c
|
||||
$(LTLINK) sqlite3_analyzer.c -o $@ $(LIBTCL) $(TLIBS)
|
||||
|
||||
dbdump$(TEXE): $(TOP)/ext/misc/dbdump.c sqlite3.lo
|
||||
$(LTLINK) -DDBDUMP_STANDALONE -o $@ \
|
||||
$(TOP)/ext/misc/dbdump.c sqlite3.lo $(TLIBS)
|
||||
|
||||
showdb$(TEXE): $(TOP)/tool/showdb.c sqlite3.lo
|
||||
$(LTLINK) -o $@ $(TOP)/tool/showdb.c sqlite3.lo $(TLIBS)
|
||||
|
||||
|
@ -2178,6 +2178,10 @@ sqlite3_analyzer.exe: sqlite3_analyzer.c $(LIBRESOBJS)
|
||||
$(LTLINK) $(NO_WARN) -DBUILD_sqlite -I$(TCLINCDIR) sqlite3_analyzer.c \
|
||||
/link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LTLIBS) $(TLIBS)
|
||||
|
||||
dbdump.exe: $(TOP)\ext\misc\dbdump.c sqlite3.c
|
||||
$(LTLINK) $(NO_WARN) -DDBDUMP_STANDALONE $(TOP)\ext\misc\dbdump.c sqlite3.c \
|
||||
/link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LTLIBS)
|
||||
|
||||
testloadext.lo: $(TOP)\src\test_loadext.c
|
||||
$(LTCOMPILE) $(NO_WARN) -c $(TOP)\src\test_loadext.c
|
||||
|
||||
|
717
ext/misc/dbdump.c
Normal file
717
ext/misc/dbdump.c
Normal file
@ -0,0 +1,717 @@
|
||||
/*
|
||||
** 2016-03-13
|
||||
**
|
||||
** 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 implements a C-language subroutine that converts the content
|
||||
** of an SQLite database into UTF-8 text SQL statements that can be used
|
||||
** to exactly recreate the original database. ROWID values are preserved.
|
||||
**
|
||||
** A prototype of the implemented subroutine is this:
|
||||
**
|
||||
** int sqlite3_db_dump(
|
||||
** sqlite3 *db,
|
||||
** const char *zSchema,
|
||||
** const char *zTable,
|
||||
** void (*xCallback)(void*, const char*),
|
||||
** void *pArg
|
||||
** );
|
||||
**
|
||||
** The db parameter is the database connection. zSchema is the schema within
|
||||
** that database which is to be dumped. Usually the zSchema is "main" but
|
||||
** can also be "temp" or any ATTACH-ed database. If zTable is not NULL, then
|
||||
** only the content of that one table is dumped. If zTable is NULL, then all
|
||||
** tables are dumped.
|
||||
**
|
||||
** The generate text is passed to xCallback() in multiple calls. The second
|
||||
** argument to xCallback() is a copy of the pArg parameter. The first
|
||||
** argument is some of the output text that this routine generates. The
|
||||
** signature to xCallback() is designed to make it compatible with fputs().
|
||||
**
|
||||
** The sqlite3_db_dump() subroutine returns SQLITE_OK on success or some error
|
||||
** code if it encounters a problem.
|
||||
**
|
||||
** If this file is compiled with -DDBDUMP_STANDALONE then a "main()" routine
|
||||
** is included so that this routine becomes a command-line utility. The
|
||||
** command-line utility takes two or three arguments which are the name
|
||||
** of the database file, the schema, and optionally the table, forming the
|
||||
** first three arguments of a single call to the library routine.
|
||||
*/
|
||||
#include "sqlite3.h"
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/*
|
||||
** The state of the dump process.
|
||||
*/
|
||||
typedef struct DState DState;
|
||||
struct DState {
|
||||
sqlite3 *db; /* The database connection */
|
||||
int nErr; /* Number of errors seen so far */
|
||||
int rc; /* Error code */
|
||||
int writableSchema; /* True if in writable_schema mode */
|
||||
int (*xCallback)(const char*,void*); /* Send output here */
|
||||
void *pArg; /* Argument to xCallback() */
|
||||
};
|
||||
|
||||
/*
|
||||
** A variable length string to which one can append text.
|
||||
*/
|
||||
typedef struct DText DText;
|
||||
struct DText {
|
||||
char *z; /* The text */
|
||||
int n; /* Number of bytes of content in z[] */
|
||||
int nAlloc; /* Number of bytes allocated to z[] */
|
||||
};
|
||||
|
||||
/*
|
||||
** Initialize and destroy a DText object
|
||||
*/
|
||||
static void initText(DText *p){
|
||||
memset(p, 0, sizeof(*p));
|
||||
}
|
||||
static void freeText(DText *p){
|
||||
sqlite3_free(p->z);
|
||||
initText(p);
|
||||
}
|
||||
|
||||
/* zIn is either a pointer to a NULL-terminated string in memory obtained
|
||||
** from malloc(), or a NULL pointer. The string pointed to by zAppend is
|
||||
** added to zIn, and the result returned in memory obtained from malloc().
|
||||
** zIn, if it was not NULL, is freed.
|
||||
**
|
||||
** If the third argument, quote, is not '\0', then it is used as a
|
||||
** quote character for zAppend.
|
||||
*/
|
||||
static void appendText(DText *p, char const *zAppend, char quote){
|
||||
int len;
|
||||
int i;
|
||||
int nAppend = (int)(strlen(zAppend) & 0x3fffffff);
|
||||
|
||||
len = nAppend+p->n+1;
|
||||
if( quote ){
|
||||
len += 2;
|
||||
for(i=0; i<nAppend; i++){
|
||||
if( zAppend[i]==quote ) len++;
|
||||
}
|
||||
}
|
||||
|
||||
if( p->n+len>=p->nAlloc ){
|
||||
char *zNew;
|
||||
p->nAlloc = p->nAlloc*2 + len + 20;
|
||||
zNew = sqlite3_realloc(p->z, p->nAlloc);
|
||||
if( zNew==0 ){
|
||||
freeText(p);
|
||||
return;
|
||||
}
|
||||
p->z = zNew;
|
||||
}
|
||||
|
||||
if( quote ){
|
||||
char *zCsr = p->z+p->n;
|
||||
*zCsr++ = quote;
|
||||
for(i=0; i<nAppend; i++){
|
||||
*zCsr++ = zAppend[i];
|
||||
if( zAppend[i]==quote ) *zCsr++ = quote;
|
||||
}
|
||||
*zCsr++ = quote;
|
||||
p->n = (int)(zCsr - p->z);
|
||||
*zCsr = '\0';
|
||||
}else{
|
||||
memcpy(p->z+p->n, zAppend, nAppend);
|
||||
p->n += nAppend;
|
||||
p->z[p->n] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Attempt to determine if identifier zName needs to be quoted, either
|
||||
** because it contains non-alphanumeric characters, or because it is an
|
||||
** SQLite keyword. Be conservative in this estimate: When in doubt assume
|
||||
** that quoting is required.
|
||||
**
|
||||
** Return '"' if quoting is required. Return 0 if no quoting is required.
|
||||
*/
|
||||
static char quoteChar(const char *zName){
|
||||
/* All SQLite keywords, in alphabetical order */
|
||||
static const char *azKeywords[] = {
|
||||
"ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND", "AS",
|
||||
"ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BY",
|
||||
"CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT",
|
||||
"CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_DATE",
|
||||
"CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE",
|
||||
"DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DROP", "EACH",
|
||||
"ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN",
|
||||
"FAIL", "FOR", "FOREIGN", "FROM", "FULL", "GLOB", "GROUP", "HAVING", "IF",
|
||||
"IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY", "INNER",
|
||||
"INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY",
|
||||
"LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", "NO", "NOT", "NOTNULL",
|
||||
"NULL", "OF", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", "PRAGMA",
|
||||
"PRIMARY", "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP",
|
||||
"REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT",
|
||||
"ROLLBACK", "ROW", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP",
|
||||
"TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", "UNION", "UNIQUE",
|
||||
"UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE",
|
||||
"WITH", "WITHOUT",
|
||||
};
|
||||
int i, lwr, upr, mid, c;
|
||||
if( !isalpha((unsigned char)zName[0]) && zName[0]!='_' ) return '"';
|
||||
for(i=0; zName[i]; i++){
|
||||
if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ) return '"';
|
||||
}
|
||||
lwr = 0;
|
||||
upr = sizeof(azKeywords)/sizeof(azKeywords[0]) - 1;
|
||||
while( lwr<=upr ){
|
||||
mid = (lwr+upr)/2;
|
||||
c = sqlite3_stricmp(azKeywords[mid], zName);
|
||||
if( c==0 ) return '"';
|
||||
if( c<0 ){
|
||||
lwr = mid+1;
|
||||
}else{
|
||||
upr = mid-1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Release memory previously allocated by tableColumnList().
|
||||
*/
|
||||
static void freeColumnList(char **azCol){
|
||||
int i;
|
||||
for(i=1; azCol[i]; i++){
|
||||
sqlite3_free(azCol[i]);
|
||||
}
|
||||
/* azCol[0] is a static string */
|
||||
sqlite3_free(azCol);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a list of pointers to strings which are the names of all
|
||||
** columns in table zTab. The memory to hold the names is dynamically
|
||||
** allocated and must be released by the caller using a subsequent call
|
||||
** to freeColumnList().
|
||||
**
|
||||
** The azCol[0] entry is usually NULL. However, if zTab contains a rowid
|
||||
** value that needs to be preserved, then azCol[0] is filled in with the
|
||||
** name of the rowid column.
|
||||
**
|
||||
** The first regular column in the table is azCol[1]. The list is terminated
|
||||
** by an entry with azCol[i]==0.
|
||||
*/
|
||||
static char **tableColumnList(DState *p, const char *zTab){
|
||||
char **azCol = 0;
|
||||
sqlite3_stmt *pStmt = 0;
|
||||
char *zSql;
|
||||
int nCol = 0;
|
||||
int nAlloc = 0;
|
||||
int nPK = 0; /* Number of PRIMARY KEY columns seen */
|
||||
int isIPK = 0; /* True if one PRIMARY KEY column of type INTEGER */
|
||||
int preserveRowid = 1;
|
||||
int rc;
|
||||
|
||||
zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab);
|
||||
if( zSql==0 ) return 0;
|
||||
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
|
||||
sqlite3_free(zSql);
|
||||
if( rc ) return 0;
|
||||
while( sqlite3_step(pStmt)==SQLITE_ROW ){
|
||||
if( nCol>=nAlloc-2 ){
|
||||
char **azNew;
|
||||
nAlloc = nAlloc*2 + nCol + 10;
|
||||
azNew = sqlite3_realloc(azCol, nAlloc*sizeof(azCol[0]));
|
||||
if( azNew==0 ) goto col_oom;
|
||||
azCol = azNew;
|
||||
azCol[0] = 0;
|
||||
}
|
||||
azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1));
|
||||
if( azCol[nCol]==0 ) goto col_oom;
|
||||
if( sqlite3_column_int(pStmt, 5) ){
|
||||
nPK++;
|
||||
if( nPK==1
|
||||
&& sqlite3_stricmp((const char*)sqlite3_column_text(pStmt,2),
|
||||
"INTEGER")==0
|
||||
){
|
||||
isIPK = 1;
|
||||
}else{
|
||||
isIPK = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(pStmt);
|
||||
pStmt = 0;
|
||||
azCol[nCol+1] = 0;
|
||||
|
||||
/* The decision of whether or not a rowid really needs to be preserved
|
||||
** is tricky. We never need to preserve a rowid for a WITHOUT ROWID table
|
||||
** or a table with an INTEGER PRIMARY KEY. We are unable to preserve
|
||||
** rowids on tables where the rowid is inaccessible because there are other
|
||||
** columns in the table named "rowid", "_rowid_", and "oid".
|
||||
*/
|
||||
if( isIPK ){
|
||||
/* If a single PRIMARY KEY column with type INTEGER was seen, then it
|
||||
** might be an alise for the ROWID. But it might also be a WITHOUT ROWID
|
||||
** table or a INTEGER PRIMARY KEY DESC column, neither of which are
|
||||
** ROWID aliases. To distinguish these cases, check to see if
|
||||
** there is a "pk" entry in "PRAGMA index_list". There will be
|
||||
** no "pk" index if the PRIMARY KEY really is an alias for the ROWID.
|
||||
*/
|
||||
zSql = sqlite3_mprintf("SELECT 1 FROM pragma_index_list(%Q)"
|
||||
" WHERE origin='pk'", zTab);
|
||||
if( zSql==0 ) goto col_oom;
|
||||
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
|
||||
sqlite3_free(zSql);
|
||||
if( rc ){
|
||||
freeColumnList(azCol);
|
||||
return 0;
|
||||
}
|
||||
rc = sqlite3_step(pStmt);
|
||||
sqlite3_finalize(pStmt);
|
||||
pStmt = 0;
|
||||
preserveRowid = rc==SQLITE_ROW;
|
||||
}
|
||||
if( preserveRowid ){
|
||||
/* Only preserve the rowid if we can find a name to use for the
|
||||
** rowid */
|
||||
static char *azRowid[] = { "rowid", "_rowid_", "oid" };
|
||||
int i, j;
|
||||
for(j=0; j<3; j++){
|
||||
for(i=1; i<=nCol; i++){
|
||||
if( sqlite3_stricmp(azRowid[j],azCol[i])==0 ) break;
|
||||
}
|
||||
if( i>nCol ){
|
||||
/* At this point, we know that azRowid[j] is not the name of any
|
||||
** ordinary column in the table. Verify that azRowid[j] is a valid
|
||||
** name for the rowid before adding it to azCol[0]. WITHOUT ROWID
|
||||
** tables will fail this last check */
|
||||
int rc;
|
||||
rc = sqlite3_table_column_metadata(p->db,0,zTab,azRowid[j],0,0,0,0,0);
|
||||
if( rc==SQLITE_OK ) azCol[0] = azRowid[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return azCol;
|
||||
|
||||
col_oom:
|
||||
sqlite3_finalize(pStmt);
|
||||
freeColumnList(azCol);
|
||||
p->nErr++;
|
||||
p->rc = SQLITE_NOMEM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Send mprintf-formatted content to the output callback.
|
||||
*/
|
||||
static void output_formatted(DState *p, const char *zFormat, ...){
|
||||
va_list ap;
|
||||
char *z;
|
||||
va_start(ap, zFormat);
|
||||
z = sqlite3_vmprintf(zFormat, ap);
|
||||
va_end(ap);
|
||||
p->xCallback(z, p->pArg);
|
||||
sqlite3_free(z);
|
||||
}
|
||||
|
||||
/*
|
||||
** Output the given string as a quoted string using SQL quoting conventions.
|
||||
**
|
||||
** The "\n" and "\r" characters are converted to char(10) and char(13)
|
||||
** to prevent them from being transformed by end-of-line translators.
|
||||
*/
|
||||
static void output_quoted_string(DState *p, const unsigned char *z){
|
||||
int i;
|
||||
char c;
|
||||
int inQuote = 0;
|
||||
int bStarted = 0;
|
||||
|
||||
for(i=0; (c = z[i])!=0 && c!='\'' && c!='\n' && c!='\r'; i++){}
|
||||
if( c==0 ){
|
||||
output_formatted(p, "'%s'", z);
|
||||
return;
|
||||
}
|
||||
while( *z ){
|
||||
for(i=0; (c = z[i])!=0 && c!='\n' && c!='\r' && c!='\''; i++){}
|
||||
if( c=='\'' ) i++;
|
||||
if( i ){
|
||||
if( !inQuote ){
|
||||
if( bStarted ) p->xCallback("||", p->pArg);
|
||||
p->xCallback("'", p->pArg);
|
||||
inQuote = 1;
|
||||
}
|
||||
output_formatted(p, "%.*s", i, z);
|
||||
z += i;
|
||||
bStarted = 1;
|
||||
}
|
||||
if( c=='\'' ){
|
||||
p->xCallback("'", p->pArg);
|
||||
continue;
|
||||
}
|
||||
if( inQuote ){
|
||||
p->xCallback("'", p->pArg);
|
||||
inQuote = 0;
|
||||
}
|
||||
if( c==0 ){
|
||||
break;
|
||||
}
|
||||
for(i=0; (c = z[i])=='\r' || c=='\n'; i++){
|
||||
if( bStarted ) p->xCallback("||", p->pArg);
|
||||
output_formatted(p, "char(%d)", c);
|
||||
bStarted = 1;
|
||||
}
|
||||
z += i;
|
||||
}
|
||||
if( inQuote ) p->xCallback("'", p->pArg);
|
||||
}
|
||||
|
||||
/*
|
||||
** This is an sqlite3_exec callback routine used for dumping the database.
|
||||
** Each row received by this callback consists of a table name,
|
||||
** the table type ("index" or "table") and SQL to create the table.
|
||||
** This routine should print text sufficient to recreate the table.
|
||||
*/
|
||||
static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){
|
||||
int rc;
|
||||
const char *zTable;
|
||||
const char *zType;
|
||||
const char *zSql;
|
||||
DState *p = (DState*)pArg;
|
||||
sqlite3_stmt *pStmt;
|
||||
|
||||
(void)azCol;
|
||||
if( nArg!=3 ) return 1;
|
||||
zTable = azArg[0];
|
||||
zType = azArg[1];
|
||||
zSql = azArg[2];
|
||||
|
||||
if( strcmp(zTable, "sqlite_sequence")==0 ){
|
||||
p->xCallback("DELETE FROM sqlite_sequence;\n", p->pArg);
|
||||
}else if( sqlite3_strglob("sqlite_stat?", zTable)==0 ){
|
||||
p->xCallback("ANALYZE sqlite_master;\n", p->pArg);
|
||||
}else if( strncmp(zTable, "sqlite_", 7)==0 ){
|
||||
return 0;
|
||||
}else if( strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){
|
||||
if( !p->writableSchema ){
|
||||
p->xCallback("PRAGMA writable_schema=ON;\n", p->pArg);
|
||||
p->writableSchema = 1;
|
||||
}
|
||||
output_formatted(p,
|
||||
"INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)"
|
||||
"VALUES('table','%q','%q',0,'%q');",
|
||||
zTable, zTable, zSql);
|
||||
return 0;
|
||||
}else{
|
||||
if( sqlite3_strglob("CREATE TABLE ['\"]*", zSql)==0 ){
|
||||
p->xCallback("CREATE TABLE IF NOT EXISTS ", p->pArg);
|
||||
p->xCallback(zSql+13, p->pArg);
|
||||
}else{
|
||||
p->xCallback(zSql, p->pArg);
|
||||
}
|
||||
p->xCallback(";\n", p->pArg);
|
||||
}
|
||||
|
||||
if( strcmp(zType, "table")==0 ){
|
||||
DText sSelect;
|
||||
DText sTable;
|
||||
char **azCol;
|
||||
int i;
|
||||
int nCol;
|
||||
|
||||
azCol = tableColumnList(p, zTable);
|
||||
if( azCol==0 ) return 0;
|
||||
|
||||
initText(&sTable);
|
||||
appendText(&sTable, "INSERT INTO ", 0);
|
||||
|
||||
/* Always quote the table name, even if it appears to be pure ascii,
|
||||
** in case it is a keyword. Ex: INSERT INTO "table" ... */
|
||||
appendText(&sTable, zTable, quoteChar(zTable));
|
||||
|
||||
/* If preserving the rowid, add a column list after the table name.
|
||||
** In other words: "INSERT INTO tab(rowid,a,b,c,...) VALUES(...)"
|
||||
** instead of the usual "INSERT INTO tab VALUES(...)".
|
||||
*/
|
||||
if( azCol[0] ){
|
||||
appendText(&sTable, "(", 0);
|
||||
appendText(&sTable, azCol[0], 0);
|
||||
for(i=1; azCol[i]; i++){
|
||||
appendText(&sTable, ",", 0);
|
||||
appendText(&sTable, azCol[i], quoteChar(azCol[i]));
|
||||
}
|
||||
appendText(&sTable, ")", 0);
|
||||
}
|
||||
appendText(&sTable, " VALUES(", 0);
|
||||
|
||||
/* Build an appropriate SELECT statement */
|
||||
initText(&sSelect);
|
||||
appendText(&sSelect, "SELECT ", 0);
|
||||
if( azCol[0] ){
|
||||
appendText(&sSelect, azCol[0], 0);
|
||||
appendText(&sSelect, ",", 0);
|
||||
}
|
||||
for(i=1; azCol[i]; i++){
|
||||
appendText(&sSelect, azCol[i], quoteChar(azCol[i]));
|
||||
if( azCol[i+1] ){
|
||||
appendText(&sSelect, ",", 0);
|
||||
}
|
||||
}
|
||||
nCol = i;
|
||||
if( azCol[0]==0 ) nCol--;
|
||||
freeColumnList(azCol);
|
||||
appendText(&sSelect, " FROM ", 0);
|
||||
appendText(&sSelect, zTable, quoteChar(zTable));
|
||||
|
||||
rc = sqlite3_prepare_v2(p->db, sSelect.z, -1, &pStmt, 0);
|
||||
if( rc!=SQLITE_OK ){
|
||||
p->nErr++;
|
||||
if( p->rc==SQLITE_OK ) p->rc = rc;
|
||||
}else{
|
||||
while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
p->xCallback(sTable.z, p->pArg);
|
||||
for(i=0; i<nCol; i++){
|
||||
if( i ) p->xCallback(",", p->pArg);
|
||||
switch( sqlite3_column_type(pStmt,i) ){
|
||||
case SQLITE_INTEGER: {
|
||||
output_formatted(p, "%lld", sqlite3_column_int64(pStmt,i));
|
||||
break;
|
||||
}
|
||||
case SQLITE_FLOAT: {
|
||||
double r = sqlite3_column_double(pStmt,i);
|
||||
output_formatted(p, "%!.20g", r);
|
||||
break;
|
||||
}
|
||||
case SQLITE_NULL: {
|
||||
p->xCallback("NULL", p->pArg);
|
||||
break;
|
||||
}
|
||||
case SQLITE_TEXT: {
|
||||
output_quoted_string(p, sqlite3_column_text(pStmt,i));
|
||||
break;
|
||||
}
|
||||
case SQLITE_BLOB: {
|
||||
int nByte = sqlite3_column_bytes(pStmt,i);
|
||||
unsigned char *a = (unsigned char*)sqlite3_column_blob(pStmt,i);
|
||||
int j;
|
||||
p->xCallback("x'", p->pArg);
|
||||
for(j=0; j<nByte; j++){
|
||||
char zWord[3];
|
||||
zWord[0] = "0123456789abcdef"[(a[j]>>4)&15];
|
||||
zWord[1] = "0123456789abcdef"[a[j]&15];
|
||||
zWord[2] = 0;
|
||||
p->xCallback(zWord, p->pArg);
|
||||
}
|
||||
p->xCallback("'", p->pArg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
p->xCallback(");\n", p->pArg);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(pStmt);
|
||||
freeText(&sTable);
|
||||
freeText(&sSelect);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Execute a query statement that will generate SQL output. Print
|
||||
** the result columns, comma-separated, on a line and then add a
|
||||
** semicolon terminator to the end of that line.
|
||||
**
|
||||
** If the number of columns is 1 and that column contains text "--"
|
||||
** then write the semicolon on a separate line. That way, if a
|
||||
** "--" comment occurs at the end of the statement, the comment
|
||||
** won't consume the semicolon terminator.
|
||||
*/
|
||||
static void output_sql_from_query(
|
||||
DState *p, /* Query context */
|
||||
const char *zSelect, /* SELECT statement to extract content */
|
||||
...
|
||||
){
|
||||
sqlite3_stmt *pSelect;
|
||||
int rc;
|
||||
int nResult;
|
||||
int i;
|
||||
const char *z;
|
||||
char *zSql;
|
||||
va_list ap;
|
||||
va_start(ap, zSelect);
|
||||
zSql = sqlite3_vmprintf(zSelect, ap);
|
||||
va_end(ap);
|
||||
if( zSql==0 ){
|
||||
p->rc = SQLITE_NOMEM;
|
||||
p->nErr++;
|
||||
return;
|
||||
}
|
||||
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pSelect, 0);
|
||||
sqlite3_free(zSql);
|
||||
if( rc!=SQLITE_OK || !pSelect ){
|
||||
output_formatted(p, "/**** ERROR: (%d) %s *****/\n", rc,
|
||||
sqlite3_errmsg(p->db));
|
||||
p->nErr++;
|
||||
return;
|
||||
}
|
||||
rc = sqlite3_step(pSelect);
|
||||
nResult = sqlite3_column_count(pSelect);
|
||||
while( rc==SQLITE_ROW ){
|
||||
z = (const char*)sqlite3_column_text(pSelect, 0);
|
||||
p->xCallback(z, p->pArg);
|
||||
for(i=1; i<nResult; i++){
|
||||
p->xCallback(",", p->pArg);
|
||||
p->xCallback((const char*)sqlite3_column_text(pSelect,i), p->pArg);
|
||||
}
|
||||
if( z==0 ) z = "";
|
||||
while( z[0] && (z[0]!='-' || z[1]!='-') ) z++;
|
||||
if( z[0] ){
|
||||
p->xCallback("\n;\n", p->pArg);
|
||||
}else{
|
||||
p->xCallback(";\n", p->pArg);
|
||||
}
|
||||
rc = sqlite3_step(pSelect);
|
||||
}
|
||||
rc = sqlite3_finalize(pSelect);
|
||||
if( rc!=SQLITE_OK ){
|
||||
output_formatted(p, "/**** ERROR: (%d) %s *****/\n", rc,
|
||||
sqlite3_errmsg(p->db));
|
||||
if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Run zQuery. Use dump_callback() as the callback routine so that
|
||||
** the contents of the query are output as SQL statements.
|
||||
**
|
||||
** If we get a SQLITE_CORRUPT error, rerun the query after appending
|
||||
** "ORDER BY rowid DESC" to the end.
|
||||
*/
|
||||
static void run_schema_dump_query(
|
||||
DState *p,
|
||||
const char *zQuery,
|
||||
...
|
||||
){
|
||||
char *zErr = 0;
|
||||
char *z;
|
||||
va_list ap;
|
||||
va_start(ap, zQuery);
|
||||
z = sqlite3_vmprintf(zQuery, ap);
|
||||
va_end(ap);
|
||||
sqlite3_exec(p->db, z, dump_callback, p, &zErr);
|
||||
sqlite3_free(z);
|
||||
if( zErr ){
|
||||
output_formatted(p, "/****** %s ******/\n", zErr);
|
||||
sqlite3_free(zErr);
|
||||
p->nErr++;
|
||||
zErr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Convert an SQLite database into SQL statements that will recreate that
|
||||
** database.
|
||||
*/
|
||||
int sqlite3_db_dump(
|
||||
sqlite3 *db, /* The database connection */
|
||||
const char *zSchema, /* Which schema to dump. Usually "main". */
|
||||
const char *zTable, /* Which table to dump. NULL means everything. */
|
||||
int (*xCallback)(const char*,void*), /* Output sent to this callback */
|
||||
void *pArg /* Second argument of the callback */
|
||||
){
|
||||
DState x;
|
||||
memset(&x, 0, sizeof(x));
|
||||
x.rc = sqlite3_exec(db, "BEGIN", 0, 0, 0);
|
||||
if( x.rc ) return x.rc;
|
||||
x.db = db;
|
||||
x.xCallback = xCallback;
|
||||
x.pArg = pArg;
|
||||
xCallback("PRAGMA foreign_keys=OFF;\nBEGIN TRANSACTION;\n", pArg);
|
||||
if( zTable==0 ){
|
||||
run_schema_dump_query(&x,
|
||||
"SELECT name, type, sql FROM \"%w\".sqlite_master "
|
||||
"WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'",
|
||||
zSchema
|
||||
);
|
||||
run_schema_dump_query(&x,
|
||||
"SELECT name, type, sql FROM \"%w\".sqlite_master "
|
||||
"WHERE name=='sqlite_sequence'", zSchema
|
||||
);
|
||||
output_sql_from_query(&x,
|
||||
"SELECT sql FROM sqlite_master "
|
||||
"WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0
|
||||
);
|
||||
}else{
|
||||
run_schema_dump_query(&x,
|
||||
"SELECT name, type, sql FROM \"%w\".sqlite_master "
|
||||
"WHERE tbl_name=%Q COLLATE nocase AND type=='table'"
|
||||
" AND sql NOT NULL",
|
||||
zSchema, zTable
|
||||
);
|
||||
output_sql_from_query(&x,
|
||||
"SELECT sql FROM \"%w\".sqlite_master "
|
||||
"WHERE sql NOT NULL"
|
||||
" AND type IN ('index','trigger','view')"
|
||||
" AND tbl_name=%Q COLLATE nocase",
|
||||
zSchema, zTable
|
||||
);
|
||||
}
|
||||
if( x.writableSchema ){
|
||||
xCallback("PRAGMA writable_schema=OFF;\n", pArg);
|
||||
}
|
||||
xCallback(x.nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n", pArg);
|
||||
sqlite3_exec(db, "COMMIT", 0, 0, 0);
|
||||
return x.rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* The generic subroutine is above. The code the follows implements
|
||||
** the command-line interface.
|
||||
*/
|
||||
#ifdef DBDUMP_STANDALONE
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
** Command-line interface
|
||||
*/
|
||||
int main(int argc, char **argv){
|
||||
sqlite3 *db;
|
||||
const char *zDb;
|
||||
const char *zSchema;
|
||||
const char *zTable = 0;
|
||||
int rc;
|
||||
|
||||
if( argc<2 || argc>4 ){
|
||||
fprintf(stderr, "Usage: %s DATABASE ?SCHEMA? ?TABLE?\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
zDb = argv[1];
|
||||
zSchema = argc>=3 ? argv[2] : "main";
|
||||
zTable = argc==4 ? argv[3] : 0;
|
||||
|
||||
rc = sqlite3_open(zDb, &db);
|
||||
if( rc ){
|
||||
fprintf(stderr, "Cannot open \"%s\": %s\n", zDb, sqlite3_errmsg(db));
|
||||
sqlite3_close(db);
|
||||
return 1;
|
||||
}
|
||||
rc = sqlite3_db_dump(db, zSchema, zTable,
|
||||
(int(*)(const char*,void*))fputs, (void*)stdout);
|
||||
if( rc ){
|
||||
fprintf(stderr, "Error: sqlite3_db_dump() returns %d\n", rc);
|
||||
}
|
||||
sqlite3_close(db);
|
||||
return rc!=SQLITE_OK;
|
||||
}
|
||||
#endif /* DBDUMP_STANDALONE */
|
4
main.mk
4
main.mk
@ -761,6 +761,10 @@ sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl
|
||||
sqlite3_analyzer$(EXE): sqlite3_analyzer.c
|
||||
$(TCCX) $(TCL_FLAGS) sqlite3_analyzer.c -o $@ $(LIBTCL) $(THREADLIB)
|
||||
|
||||
dbdump$(EXE): $(TOP)/ext/misc/dbdump.c sqlite3.o
|
||||
$(TCCX) -DDBDUMP_STANDALONE -o dbdump$(EXE) \
|
||||
$(TOP)/ext/misc/dbdump.c sqlite3.o $(THREADLIB)
|
||||
|
||||
# Rules to build the 'testfixture' application.
|
||||
#
|
||||
TESTFIXTURE_FLAGS = -DSQLITE_TEST=1 -DSQLITE_CRASH_TEST=1
|
||||
|
19
manifest
19
manifest
@ -1,8 +1,8 @@
|
||||
C In\sthe\soutput\sof\sthe\s".dump"\scommand\sin\sthe\sCLI,\squote\snewline\sand\ncarriage-return\scharacters\susing\sthe\schar()\sfunction,\sso\sthat\sthey\sdo\snot\nget\seaten\sby\send-of-line\sprocessing\slogic\sin\sthe\sOS\sor\sin\sother\scommand-line\nutilities\sand/or\slibraries.
|
||||
D 2017-03-13T18:24:06.748
|
||||
F Makefile.in 2dae2a56457c2885425a480e1053de8096aff924
|
||||
C Add\sthe\sdbdump.c\sextension\sthat\simplements\sfunctionality\ssimilar\sto\sthe\n".dump"\scommand\sof\sthe\sCLI,\sthough\sin\sa\sseparate\slibrary.
|
||||
D 2017-03-13T22:10:05.684
|
||||
F Makefile.in 9605f4c49eace601d5c12c85dd6e037cc613a6d823e857614ba26b42f1285db0
|
||||
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
|
||||
F Makefile.msc 9020fa41eb91f657ae0cc44145d0a2f3af520860
|
||||
F Makefile.msc 5f1194a315c341c14870534652afd34bfa701219e5994fa0ee0bac91389fb740
|
||||
F README.md 8ecc12493ff9f820cdea6520a9016001cb2e59b7
|
||||
F VERSION 3605fa447e4623f5ff4a6adc97b1fde9a257b8f2
|
||||
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
|
||||
@ -211,6 +211,7 @@ F ext/misc/carray.c 40c27641010a4dc67e3690bdb7c9d36ca58b3c2d
|
||||
F ext/misc/closure.c 0d2a038df8fbae7f19de42e7c7d71f2e4dc88704
|
||||
F ext/misc/compress.c 122faa92d25033d6c3f07c39231de074ab3d2e83
|
||||
F ext/misc/csv.c 531a46cbad789fca0aa9db69a0e6c8ac9e68767d
|
||||
F ext/misc/dbdump.c 34174d537318027b159e3341105866c05a4149376ede59988c82280bed144e6d
|
||||
F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2
|
||||
F ext/misc/fileio.c d4171c815d6543a9edef8308aab2951413cd8d0f
|
||||
F ext/misc/fuzzer.c 7c64b8197bb77b7d64eff7cac7848870235d4c25
|
||||
@ -322,7 +323,7 @@ F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
|
||||
F main.mk 0ec10b604f4668f7e85a358954babe75c94dc0d5
|
||||
F main.mk 9abb506e717887d57f754bae139b85c1a06d6f2ac25b589f3e792e310567f278
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
|
||||
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
|
||||
@ -1563,8 +1564,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 9034cf7efc603864f51e931c7dc4fbbc2d01904e951e78c88d4d80f9936250e8 8b2954dd8376e2de985cf5dedeb6eec32c430505
|
||||
R 73c5fbe608fb0e20ee39173a6198ea04
|
||||
T +closed 8b2954dd8376e2de985cf5dedeb6eec32c430505
|
||||
P 68f6dc7af1013f296a11db14c007cc13cc3fe56832848bfed835ed8f74dcc676 59241a50ad1d6fe9f5804b73b3467dcd407d359ccbdcb9d72f2f6d94c90c1f40
|
||||
R 6996c48c318cda176124bead4ce6093a
|
||||
T +closed 59241a50ad1d6fe9f5804b73b3467dcd407d359ccbdcb9d72f2f6d94c90c1f40
|
||||
U drh
|
||||
Z 5e4a79f0eabba6013ee57f9716ad3797
|
||||
Z 2f55f00bb27f982c54090f39d62e41f0
|
||||
|
@ -1 +1 @@
|
||||
68f6dc7af1013f296a11db14c007cc13cc3fe56832848bfed835ed8f74dcc676
|
||||
2b9980a292d7f0d995b77d9b3fb217bb2206f6d452e036ac502fa06561af4c12
|
Loading…
x
Reference in New Issue
Block a user