From: drh Date: Wed, 8 Mar 2017 12:25:18 +0000 (+0000) Subject: In the CLI, avoid unnecessary identifier quoting in the ".dump" output. X-Git-Tag: version-3.18.0~72 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f42d31800240fd332de77a7c65e4863dea0dd84a;p=thirdparty%2Fsqlite.git In the CLI, avoid unnecessary identifier quoting in the ".dump" output. Also add new ".dump" test cases. FossilOrigin-Name: de65f907610a59e64cbf2214789c11f7117a86a6 --- diff --git a/manifest b/manifest index 4a60c5aefa..92484569a1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sthe\s--preserve-rowids\soption\sto\sthe\s".dump"\scommand\sin\sthe\sCLI. -D 2017-03-08T11:44:00.069 +C In\sthe\sCLI,\savoid\sunnecessary\sidentifier\squoting\sin\sthe\s".dump"\soutput.\nAlso\sadd\snew\s".dump"\stest\scases. +D 2017-03-08T12:25:18.771 F Makefile.in edb6bcdd37748d2b1c3422ff727c748df7ffe918 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc a89ea37ab5928026001569f056973b9059492fe2 @@ -399,7 +399,7 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 F src/resolve.c 3e518b962d932a997fae373366880fc028c75706 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac F src/select.c d12f3539f80db38b09015561b569e0eb1c4b6c5f -F src/shell.c c42c3031f715712a0cd47d8f08bd2d1dfec8baa0 +F src/shell.c d1ba571e8325f727f0c9571079e27b8a4595d6fd F src/sqlite.h.in 4d0c08f8640c586564a7032b259c5f69bf397850 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 8648034aa702469afb553231677306cc6492a1ae @@ -1110,7 +1110,7 @@ F test/sharedA.test 0cdf1a76dfa00e6beee66af5b534b1e8df2720f5 F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e F test/shared_err.test 2f2aee20db294b9924e81f6ccbe60f19e21e8506 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test 5df739ee2fe5c9dbbb2f9a0158b9723bda0910b8 +F test/shell1.test bc1ca4161e1f459c9cd412d3f21f4ee635cf2322 F test/shell2.test e242a9912f44f4c23c3d1d802a83e934e84c853b F test/shell3.test 9b95ba643eaa228376f06a898fb410ee9b6e57c1 F test/shell4.test 89ad573879a745974ff2df20ff97c5d6ffffbd5d @@ -1563,7 +1563,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 961e79da73b4550b3e5b0f9a617133a76485db67 -R c754df2de2ef21ae6643ebeaf45e8d82 +P c60aee24714a47ce12ee2a4dcefb9f55211d3761 +R b3f6f7a6c1a0fc140ee14b2a6ca3bfe0 U drh -Z bce538a1e35be638681ea14d388ad037 +Z f9eff36eb51a780bc84edce006ee1256 diff --git a/manifest.uuid b/manifest.uuid index 7002a8bc75..775616cedc 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c60aee24714a47ce12ee2a4dcefb9f55211d3761 \ No newline at end of file +de65f907610a59e64cbf2214789c11f7117a86a6 \ No newline at end of file diff --git a/src/shell.c b/src/shell.c index 07f977d46a..321a39f54b 100644 --- a/src/shell.c +++ b/src/shell.c @@ -555,6 +555,123 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ } 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; in+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; in = (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) /* @@ -1274,6 +1391,7 @@ static int callback(void *pArg, int nArg, char **azArg, char **azCol){ 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 @@ -1281,7 +1399,7 @@ static int callback(void *pArg, int nArg, char **azArg, char **azCol){ */ static void set_table_name(ShellState *p, const char *zName){ int i, n; - int needQuote; + int cQuote; char *z; if( p->zDestTable ){ @@ -1289,86 +1407,24 @@ static void set_table_name(ShellState *p, const char *zName){ 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; in+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; in = (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 @@ -2131,9 +2187,6 @@ static char **tableColumnList(ShellState *p, const char *zTab){ return azCol; } - - - /* ** This is a different callback routine used for dumping the database. ** Each row received by this callback consists of a table name, @@ -2177,8 +2230,8 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ } if( strcmp(zType, "table")==0 ){ - ShellString sSelect; - ShellString sTable; + ShellText sSelect; + ShellText sTable; char **azCol; int i; char *savedDestTable; @@ -2192,8 +2245,8 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ /* 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(...)". @@ -2203,27 +2256,27 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ 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; @@ -2232,8 +2285,8 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ 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; diff --git a/test/shell1.test b/test/shell1.test index 0912fa4240..7f282292c0 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -740,41 +740,46 @@ do_test shell1-4.1 { } {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 @@ -788,14 +793,80 @@ do_test shell1-4.1.2 { } {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" #