}
return zResult;
}
+/*
+** A variable length string to which one can append text.
+*/
+typedef struct ShellText ShellText;
+struct ShellText {
+ char *z;
+ int n;
+ int nAlloc;
+};
+
+/*
+** Initialize and destroy a ShellText object
+*/
+static void initText(ShellText *p){
+ memset(p, 0, sizeof(*p));
+}
+static void freeText(ShellText *p){
+ 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(ShellText *p, char const *zAppend, char quote){
+ int len;
+ int i;
+ int nAppend = strlen30(zAppend);
+
+ 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 ){
+ p->nAlloc = p->nAlloc*2 + len + 20;
+ p->z = realloc(p->z, p->nAlloc);
+ if( p->z==0 ){
+ memset(p, 0, sizeof(*p));
+ return;
+ }
+ }
+
+ 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;
+}
#if defined(SQLITE_ENABLE_SESSION)
/*
return shell_callback(pArg, nArg, azArg, azCol, NULL);
}
+
/*
** Set the destination table field of the ShellState structure to
** the name of the table given. Escape any quote characters in the
*/
static void set_table_name(ShellState *p, const char *zName){
int i, n;
- int needQuote;
+ int cQuote;
char *z;
if( p->zDestTable ){
p->zDestTable = 0;
}
if( zName==0 ) return;
- needQuote = !isalpha((unsigned char)*zName) && *zName!='_';
- for(i=n=0; zName[i]; i++, n++){
- if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ){
- needQuote = 1;
- if( zName[i]=='\'' ) n++;
- }
- }
- if( needQuote ) n += 2;
+ cQuote = quoteChar(zName);
+ n = strlen30(zName);
+ if( cQuote ) n += 2;
z = p->zDestTable = malloc( n+1 );
if( z==0 ){
raw_printf(stderr,"Error: out of memory\n");
exit(1);
}
n = 0;
- if( needQuote ) z[n++] = '\'';
+ if( cQuote ) z[n++] = cQuote;
for(i=0; zName[i]; i++){
z[n++] = zName[i];
- if( zName[i]=='\'' ) z[n++] = '\'';
+ if( zName[i]==cQuote ) z[n++] = cQuote;
}
- if( needQuote ) z[n++] = '\'';
+ if( cQuote ) z[n++] = cQuote;
z[n] = 0;
}
-/*
-** A variable length string to which one can append text.
-*/
-typedef struct ShellString ShellString;
-struct ShellString {
- char *z;
- int n;
- int nAlloc;
-};
-
-/* 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(ShellString *p, char const *zAppend, char quote){
- int len;
- int i;
- int nAppend = strlen30(zAppend);
-
- 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 ){
- p->nAlloc = p->nAlloc*2 + len + 20;
- p->z = realloc(p->z, p->nAlloc);
- if( p->z==0 ){
- memset(p, 0, sizeof(*p));
- return;
- }
- }
-
- 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';
- }
-}
-
/*
** Execute a query statement that will generate SQL output. Print
return azCol;
}
-
-
-
/*
** This is a different callback routine used for dumping the database.
** Each row received by this callback consists of a table name,
}
if( strcmp(zType, "table")==0 ){
- ShellString sSelect;
- ShellString sTable;
+ ShellText sSelect;
+ ShellText sTable;
char **azCol;
int i;
char *savedDestTable;
/* Always quote the table name, even if it appears to be pure ascii,
** in case it is a keyword. Ex: INSERT INTO "table" ... */
- memset(&sTable, 0, sizeof(sTable));
- appendText(&sTable, zTable, '"');
+ initText(&sTable);
+ 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(...)".
appendText(&sTable, azCol[0], 0);
for(i=1; azCol[i]; i++){
appendText(&sTable, ",", 0);
- appendText(&sTable, azCol[i], '"');
+ appendText(&sTable, azCol[i], quoteChar(azCol[i]));
}
appendText(&sTable, ")", 0);
}
/* Build an appropriate SELECT statement */
- memset(&sSelect, 0, sizeof(sSelect));
+ 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], 0);
+ appendText(&sSelect, azCol[i], quoteChar(azCol[i]));
if( azCol[i+1] ){
appendText(&sSelect, ",", 0);
}
}
freeColumnList(azCol);
appendText(&sSelect, " FROM ", 0);
- appendText(&sSelect, zTable, '"');
+ appendText(&sSelect, zTable, quoteChar(zTable));
savedDestTable = p->zDestTable;
savedMode = p->mode;
rc = shell_exec(p->db, sSelect.z, shell_callback, p, 0);
p->zDestTable = savedDestTable;
p->mode = savedMode;
- free(sTable.z);
- free(sSelect.z);
+ freeText(&sTable);
+ freeText(&sSelect);
if( rc ) p->nErr++;
}
return 0;
} {0 {PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE t1(x);
-INSERT INTO "t1" VALUES(NULL);
-INSERT INTO "t1" VALUES('');
-INSERT INTO "t1" VALUES(1);
-INSERT INTO "t1" VALUES(2.25);
-INSERT INTO "t1" VALUES('hello');
-INSERT INTO "t1" VALUES(X'807f');
+INSERT INTO t1 VALUES(NULL);
+INSERT INTO t1 VALUES('');
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 VALUES(2.25);
+INSERT INTO t1 VALUES('hello');
+INSERT INTO t1 VALUES(X'807f');
CREATE TABLE t3(x,y);
-INSERT INTO "t3" VALUES(1,NULL);
-INSERT INTO "t3" VALUES(2,'');
-INSERT INTO "t3" VALUES(3,1);
-INSERT INTO "t3" VALUES(4,2.25);
-INSERT INTO "t3" VALUES(5,'hello');
-INSERT INTO "t3" VALUES(6,X'807f');
+INSERT INTO t3 VALUES(1,NULL);
+INSERT INTO t3 VALUES(2,'');
+INSERT INTO t3 VALUES(3,1);
+INSERT INTO t3 VALUES(4,2.25);
+INSERT INTO t3 VALUES(5,'hello');
+INSERT INTO t3 VALUES(6,X'807f');
COMMIT;}}
+# The --preserve-rowids option to .dump
+#
do_test shell1-4.1.1 {
catchcmd test.db {.dump --preserve-rowids}
} {0 {PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE t1(x);
-INSERT INTO "t1"(rowid,"x") VALUES(1,NULL);
-INSERT INTO "t1"(rowid,"x") VALUES(2,'');
-INSERT INTO "t1"(rowid,"x") VALUES(3,1);
-INSERT INTO "t1"(rowid,"x") VALUES(4,2.25);
-INSERT INTO "t1"(rowid,"x") VALUES(5,'hello');
-INSERT INTO "t1"(rowid,"x") VALUES(6,X'807f');
+INSERT INTO t1(rowid,x) VALUES(1,NULL);
+INSERT INTO t1(rowid,x) VALUES(2,'');
+INSERT INTO t1(rowid,x) VALUES(3,1);
+INSERT INTO t1(rowid,x) VALUES(4,2.25);
+INSERT INTO t1(rowid,x) VALUES(5,'hello');
+INSERT INTO t1(rowid,x) VALUES(6,X'807f');
CREATE TABLE t3(x,y);
-INSERT INTO "t3"(rowid,"x","y") VALUES(1,1,NULL);
-INSERT INTO "t3"(rowid,"x","y") VALUES(2,2,'');
-INSERT INTO "t3"(rowid,"x","y") VALUES(3,3,1);
-INSERT INTO "t3"(rowid,"x","y") VALUES(4,4,2.25);
-INSERT INTO "t3"(rowid,"x","y") VALUES(5,5,'hello');
-INSERT INTO "t3"(rowid,"x","y") VALUES(6,6,X'807f');
+INSERT INTO t3(rowid,x,y) VALUES(1,1,NULL);
+INSERT INTO t3(rowid,x,y) VALUES(2,2,'');
+INSERT INTO t3(rowid,x,y) VALUES(3,3,1);
+INSERT INTO t3(rowid,x,y) VALUES(4,4,2.25);
+INSERT INTO t3(rowid,x,y) VALUES(5,5,'hello');
+INSERT INTO t3(rowid,x,y) VALUES(6,6,X'807f');
COMMIT;}}
+# If the table contains an INTEGER PRIMARY KEY, do not record a separate
+# rowid column in the output.
+#
do_test shell1-4.1.2 {
db close
forcedelete test2.db
} {0 {PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
-INSERT INTO "t1" VALUES(1,NULL);
-INSERT INTO "t1" VALUES(2,'');
-INSERT INTO "t1" VALUES(3,1);
-INSERT INTO "t1" VALUES(4,2.25);
-INSERT INTO "t1" VALUES(5,'hello');
-INSERT INTO "t1" VALUES(6,X'807f');
+INSERT INTO t1 VALUES(1,NULL);
+INSERT INTO t1 VALUES(2,'');
+INSERT INTO t1 VALUES(3,1);
+INSERT INTO t1 VALUES(4,2.25);
+INSERT INTO t1 VALUES(5,'hello');
+INSERT INTO t1 VALUES(6,X'807f');
+COMMIT;}}
+
+# Verify that the table named [table] is correctly quoted and that
+# an INTEGER PRIMARY KEY DESC is not an alias for the rowid.
+#
+do_test shell1-4.1.3 {
+ db close
+ forcedelete test2.db
+ sqlite3 db test2.db
+ db eval {
+ CREATE TABLE [table](x INTEGER PRIMARY KEY DESC, y);
+ INSERT INTO [table] VALUES(1,null), (12,''), (23,1),
+ (34,2.25), (45,'hello'), (56,x'807f');
+ }
+ catchcmd test2.db {.dump --preserve-rowids}
+} {0 {PRAGMA foreign_keys=OFF;
+BEGIN TRANSACTION;
+CREATE TABLE [table](x INTEGER PRIMARY KEY DESC, y);
+INSERT INTO "table"(rowid,x,y) VALUES(1,1,NULL);
+INSERT INTO "table"(rowid,x,y) VALUES(2,12,'');
+INSERT INTO "table"(rowid,x,y) VALUES(3,23,1);
+INSERT INTO "table"(rowid,x,y) VALUES(4,34,2.25);
+INSERT INTO "table"(rowid,x,y) VALUES(5,45,'hello');
+INSERT INTO "table"(rowid,x,y) VALUES(6,56,X'807f');
COMMIT;}}
+# Do not record rowids for a WITHOUT ROWID table. Also check correct quoting
+# of table names that contain odd characters.
+#
+do_test shell1-4.1.4 {
+ db close
+ forcedelete test2.db
+ sqlite3 db test2.db
+ db eval {
+ CREATE TABLE [ta<>ble](x INTEGER PRIMARY KEY, y) WITHOUT ROWID;
+ INSERT INTO [ta<>ble] VALUES(1,null), (12,''), (23,1),
+ (34,2.25), (45,'hello'), (56,x'807f');
+ }
+ catchcmd test2.db {.dump --preserve-rowids}
+} {0 {PRAGMA foreign_keys=OFF;
+BEGIN TRANSACTION;
+CREATE TABLE [ta<>ble](x INTEGER PRIMARY KEY, y) WITHOUT ROWID;
+INSERT INTO "ta<>ble" VALUES(1,NULL);
+INSERT INTO "ta<>ble" VALUES(12,'');
+INSERT INTO "ta<>ble" VALUES(23,1);
+INSERT INTO "ta<>ble" VALUES(34,2.25);
+INSERT INTO "ta<>ble" VALUES(45,'hello');
+INSERT INTO "ta<>ble" VALUES(56,X'807f');
+COMMIT;}}
+
+# Do not record rowids if the rowid is inaccessible
+#
+do_test shell1-4.1.5 {
+ db close
+ forcedelete test2.db
+ sqlite3 db test2.db
+ db eval {
+ CREATE TABLE t1(_ROWID_,rowid,oid);
+ INSERT INTO t1 VALUES(1,null,'alpha'), (12,'',99), (23,1,x'b0b1b2');
+ }
+ catchcmd test2.db {.dump --preserve-rowids}
+} {0 {PRAGMA foreign_keys=OFF;
+BEGIN TRANSACTION;
+CREATE TABLE t1(_ROWID_,rowid,oid);
+INSERT INTO t1 VALUES(1,NULL,'alpha');
+INSERT INTO t1 VALUES(12,'',99);
+INSERT INTO t1 VALUES(23,1,X'b0b1b2');
+COMMIT;}}
# Test the output of ".mode insert"
#