Index: Makefile.msc ================================================================== --- Makefile.msc +++ Makefile.msc @@ -2089,11 +2089,11 @@ .\mkkeywordhash.exe > keywordhash.h # Source files that go into making shell.c SHELL_SRC = \ $(TOP)\src\shell.c.in \ - $(TOP)\ext\misc\appendvfs.c \ + $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\shathree.c \ $(TOP)\ext\misc\fileio.c \ $(TOP)\ext\misc\completion.c \ $(TOP)\ext\expert\sqlite3expert.c \ $(TOP)\ext\expert\sqlite3expert.h \ Index: ext/misc/fileio.c ================================================================== --- ext/misc/fileio.c +++ ext/misc/fileio.c @@ -156,10 +156,101 @@ zMsg = sqlite3_vmprintf(zFmt, ap); sqlite3_result_error(ctx, zMsg, -1); sqlite3_free(zMsg); va_end(ap); } + +#if defined(_WIN32) +/* +** This function is designed to convert a Win32 FILETIME structure into the +** number of seconds since the Unix Epoch (1970-01-01 00:00:00 UTC). +*/ +static sqlite3_uint64 fileTimeToUnixTime( + LPFILETIME pFileTime +){ + SYSTEMTIME epochSystemTime; + ULARGE_INTEGER epochIntervals; + FILETIME epochFileTime; + ULARGE_INTEGER fileIntervals; + + memset(&epochSystemTime, 0, sizeof(SYSTEMTIME)); + epochSystemTime.wYear = 1970; + epochSystemTime.wMonth = 1; + epochSystemTime.wDay = 1; + SystemTimeToFileTime(&epochSystemTime, &epochFileTime); + epochIntervals.LowPart = epochFileTime.dwLowDateTime; + epochIntervals.HighPart = epochFileTime.dwHighDateTime; + + fileIntervals.LowPart = pFileTime->dwLowDateTime; + fileIntervals.HighPart = pFileTime->dwHighDateTime; + + return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000; +} + +/* +** This function attempts to normalize the time values found in the stat() +** buffer to UTC. This is necessary on Win32, where the runtime library +** appears to return these values as local times. +*/ +static void statTimesToUtc( + const char *zPath, + struct stat *pStatBuf +){ + HANDLE hFindFile; + WIN32_FIND_DATAW fd; + LPWSTR zUnicodeName; + extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); + zUnicodeName = sqlite3_win32_utf8_to_unicode(zPath); + if( zUnicodeName ){ + memset(&fd, 0, sizeof(WIN32_FIND_DATA)); + hFindFile = FindFirstFileW(zUnicodeName, &fd); + if( hFindFile!=NULL ){ + pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime); + pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime); + pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime); + FindClose(hFindFile); + } + sqlite3_free(zUnicodeName); + } +} +#endif + +/* +** This function is used in place of stat(). On Windows, special handling +** is required in order for the included time to be returned as UTC. On all +** other systems, this function simply calls stat(). +*/ +static int fileStat( + const char *zPath, + struct stat *pStatBuf +){ +#if defined(_WIN32) + int rc = stat(zPath, pStatBuf); + if( rc==0 ) statTimesToUtc(zPath, pStatBuf); + return rc; +#else + return stat(zPath, pStatBuf); +#endif +} + +/* +** This function is used in place of lstat(). On Windows, special handling +** is required in order for the included time to be returned as UTC. On all +** other systems, this function simply calls lstat(). +*/ +static int fileLinkStat( + const char *zPath, + struct stat *pStatBuf +){ +#if defined(_WIN32) + int rc = lstat(zPath, pStatBuf); + if( rc==0 ) statTimesToUtc(zPath, pStatBuf); + return rc; +#else + return lstat(zPath, pStatBuf); +#endif +} /* ** Argument zFile is the name of a file that will be created and/or written ** by SQL function writefile(). This function ensures that the directory ** zFile will be written to exists, creating it if required. The permissions @@ -188,11 +279,11 @@ for(; zCopy[i]!='/' && i> 32; zUnicodeName = sqlite3_win32_utf8_to_unicode(zFile); + if( zUnicodeName==0 ){ + return 1; + } hFile = CreateFileW( zUnicodeName, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); sqlite3_free(zUnicodeName); @@ -289,11 +383,11 @@ CloseHandle(hFile); return !bResult; }else{ return 1; } -#elif defined(AT_FDCWD) && 0 /* utimensat() is not univerally available */ +#elif defined(AT_FDCWD) && 0 /* utimensat() is not universally available */ /* Recent unix */ struct timespec times[2]; times[0].tv_nsec = times[1].tv_nsec = 0; times[0].tv_sec = time(0); times[1].tv_sec = mtime; @@ -566,11 +660,11 @@ if( pEntry->d_name[1]=='\0' ) continue; } sqlite3_free(pCur->zPath); pCur->zPath = sqlite3_mprintf("%s/%s", pLvl->zDir, pEntry->d_name); if( pCur->zPath==0 ) return SQLITE_NOMEM; - if( lstat(pCur->zPath, &pCur->sStat) ){ + if( fileLinkStat(pCur->zPath, &pCur->sStat) ){ fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); return SQLITE_ERROR; } return SQLITE_OK; } @@ -700,11 +794,11 @@ } if( pCur->zPath==0 ){ return SQLITE_NOMEM; } - if( lstat(pCur->zPath, &pCur->sStat) ){ + if( fileLinkStat(pCur->zPath, &pCur->sStat) ){ fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); return SQLITE_ERROR; } return SQLITE_OK; Index: ext/misc/zipfile.c ================================================================== --- ext/misc/zipfile.c +++ ext/misc/zipfile.c @@ -1497,10 +1497,23 @@ if( pVal==0 || sqlite3_value_type(pVal)==SQLITE_NULL ){ return zipfileTime(); } return (u32)sqlite3_value_int64(pVal); } + +/* +** Unless it is NULL, entry pOld is currently part of the pTab->pFirstEntry +** linked list. Remove it from the list and free the object. +*/ +static void zipfileRemoveEntryFromList(ZipfileTab *pTab, ZipfileEntry *pOld){ + if( pOld ){ + ZipfileEntry **pp; + for(pp=&pTab->pFirstEntry; (*pp)!=pOld; pp=&((*pp)->pNext)); + *pp = (*pp)->pNext; + zipfileEntryFree(pOld); + } +} /* ** xUpdate method. */ static int zipfileUpdate( @@ -1522,10 +1535,12 @@ int nData = 0; /* Size of pData buffer in bytes */ int iMethod = 0; /* Compression method for new entry */ u8 *pFree = 0; /* Free this */ char *zFree = 0; /* Also free this */ ZipfileEntry *pOld = 0; + ZipfileEntry *pOld2 = 0; + int bUpdate = 0; /* True for an update that modifies "name" */ int bIsDir = 0; u32 iCrc32 = 0; if( pTab->pWriteFd==0 ){ rc = zipfileBegin(pVtab); @@ -1534,10 +1549,16 @@ /* If this is a DELETE or UPDATE, find the archive entry to delete. */ if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ const char *zDelete = (const char*)sqlite3_value_text(apVal[0]); int nDelete = (int)strlen(zDelete); + if( nVal>1 ){ + const char *zUpdate = (const char*)sqlite3_value_text(apVal[1]); + if( zUpdate && zipfileComparePath(zUpdate, zDelete, nDelete)!=0 ){ + bUpdate = 1; + } + } for(pOld=pTab->pFirstEntry; 1; pOld=pOld->pNext){ if( zipfileComparePath(pOld->cds.zFile, zDelete, nDelete)==0 ){ break; } assert( pOld->pNext ); @@ -1611,21 +1632,22 @@ zPath = (const char*)zFree; nPath++; } } - /* Check that we're not inserting a duplicate entry */ - if( pOld==0 && rc==SQLITE_OK ){ + /* Check that we're not inserting a duplicate entry -OR- updating an + ** entry with a path, thereby making it into a duplicate. */ + if( (pOld==0 || bUpdate) && rc==SQLITE_OK ){ ZipfileEntry *p; for(p=pTab->pFirstEntry; p; p=p->pNext){ if( zipfileComparePath(p->cds.zFile, zPath, nPath)==0 ){ switch( sqlite3_vtab_on_conflict(pTab->db) ){ case SQLITE_IGNORE: { goto zipfile_update_done; } case SQLITE_REPLACE: { - pOld = p; + pOld2 = p; break; } default: { zipfileTableErr(pTab, "duplicate name: \"%s\"", zPath); rc = SQLITE_CONSTRAINT; @@ -1659,22 +1681,21 @@ zipfileAddEntry(pTab, pOld, pNew); } } } - if( rc==SQLITE_OK && pOld ){ - ZipfileEntry **pp; + if( rc==SQLITE_OK && (pOld || pOld2) ){ ZipfileCsr *pCsr; for(pCsr=pTab->pCsrList; pCsr; pCsr=pCsr->pCsrNext){ - if( pCsr->pCurrent==pOld ){ - pCsr->pCurrent = pOld->pNext; + if( pCsr->pCurrent && (pCsr->pCurrent==pOld || pCsr->pCurrent==pOld2) ){ + pCsr->pCurrent = pCsr->pCurrent->pNext; pCsr->bNoop = 1; } } - for(pp=&pTab->pFirstEntry; (*pp)!=pOld; pp=&((*pp)->pNext)); - *pp = (*pp)->pNext; - zipfileEntryFree(pOld); + + zipfileRemoveEntryFromList(pTab, pOld); + zipfileRemoveEntryFromList(pTab, pOld2); } zipfile_update_done: sqlite3_free(pFree); sqlite3_free(zFree); Index: test/dbstatus2.test ================================================================== --- test/dbstatus2.test +++ test/dbstatus2.test @@ -108,8 +108,8 @@ do_execsql_test 3.2 { PRAGMA journal_mode=DELETE; PRAGMA cache_size=3; UPDATE t1 SET b=randomblob(1000); } {delete} -do_test 3.2 { db_spill db 0 } {0 8 0} +do_test 3.3 { db_spill db 0 } {0 8 0} finish_test Index: test/trace3.test ================================================================== --- test/trace3.test +++ test/trace3.test @@ -127,21 +127,21 @@ execsql { SELECT a, b FROM t1 ORDER BY a; } set stmt [lindex [lindex $::stmtlist(record) 0] 0] set ns [lindex [lindex $::stmtlist(record) 0] 1] - list $stmt [expr {$ns >= 0 && $ns <= 1000000}]; # less than 0.001 second + list $stmt [expr {$ns >= 0 && $ns <= 9999999}]; # less than 0.010 seconds } {/^-?\d+ 1$/} do_test trace3-4.4 { set ::stmtlist(record) {} db trace_v2 trace_v2_record 2 execsql { SELECT a, b FROM t1 ORDER BY a; } set stmt [lindex [lindex $::stmtlist(record) 0] 0] set ns [lindex [lindex $::stmtlist(record) 0] 1] - list $stmt [expr {$ns >= 0 && $ns <= 1000000}]; # less than 0.001 second + list $stmt [expr {$ns >= 0 && $ns <= 9999999}]; # less than 0.010 seconds } {/^-?\d+ 1$/} do_test trace3-5.1 { set ::stmtlist(record) {} db trace_v2 trace_v2_record row Index: test/zipfile.test ================================================================== --- test/zipfile.test +++ test/zipfile.test @@ -7,10 +7,12 @@ # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # + +package require Tcl 8.6 set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix zipfile @@ -18,10 +20,14 @@ finish_test; return } if {[catch {load_static_extension db zipfile} error]} { puts "Skipping zipfile tests, hit load error: $error" finish_test; return +} +if {[catch {load_static_extension db fileio} error]} { + puts "Skipping zipfile tests, hit load error: $error" + finish_test; return } proc readfile {f} { set fd [open $f] fconfigure $fd -translation binary -encoding binary @@ -28,20 +34,43 @@ set data [read $fd] close $fd set data } -if {$::tcl_platform(platform)=="unix" && [catch {exec unzip}]==0} { - set ::UNZIP 1 - load_static_extension db fileio +unset -nocomplain ::UNZIP + +if {[catch {exec unzip} msg]==0 && \ + [regexp -line {^UnZip \d+\.\d+ .*? Info-ZIP\.} $msg]} { + set ::UNZIP unzip + proc fix_stat_mode {name mode} { + if {$::tcl_platform(platform)=="windows"} { + # + # NOTE: Set or unset the write bits of the file permissions + # based on the read-only attribute because the Win32 + # version of UnZip does this. + # + set writebits 0x12; # 0o22 + set result $mode + if {[file attributes $name -readonly]} { + set result [expr {$result | $writebits}] + } else { + set result [expr {$result & ~$writebits}] + } + return $result + } else { + return $mode + } + } proc do_unzip {file} { forcedelete test_unzip file mkdir test_unzip - exec unzip -d test_unzip $file - - set res [db eval { - SELECT replace(name,'test_unzip/',''),mode,mtime,data + exec $::UNZIP -d test_unzip $file + + db func modefix fix_stat_mode + + set res [db eval { + SELECT replace(name,'test_unzip/',''),modefix(name,mode),mtime,data FROM fsdir('test_unzip') WHERE name!='test_unzip' ORDER BY name }] set res @@ -106,11 +135,10 @@ # # Then tests that unpacking the new archive using [unzip] produces # the same results as in (1). # proc do_unzip_test {tn file} { - if {[info vars ::UNZIP]==""} { return } db func sss strip_slash db eval { SELECT writefile('test_unzip.zip', ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) ) @@ -244,80 +272,88 @@ } { f.txt 33188 1000000000 abcde 0 h.txt 33188 1000000004 aaaaaaaaaabbbbbbbbbb 8 i.txt 33188 4 zxcvb 0 } + +if {$::tcl_platform(platform)=="unix"} { + set modes -rw-r--r-x + set perms 33189 +} else { + set modes -rw-r--r--; # no execute bits on Win32 + set perms 33188 +} do_execsql_test 1.6.3 { - UPDATE zz SET mode='-rw-r--r-x' WHERE name='h.txt'; + UPDATE zz SET mode=$modes WHERE name='h.txt'; SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); -} { +} [string map [list %perms% $perms] { f.txt 33188 1000000000 abcde 0 - h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8 i.txt 33188 4 zxcvb 0 -} +}] do_zip_tests 1.6.3a test.zip do_execsql_test 1.6.4 { UPDATE zz SET name = 'blue.txt' WHERE name='f.txt'; SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); -} { +} [string map [list %perms% $perms] { blue.txt 33188 1000000000 abcde 0 - h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8 i.txt 33188 4 zxcvb 0 -} +}] do_zip_tests 1.6.4a test.zip do_execsql_test 1.6.5 { UPDATE zz SET data = 'edcba' WHERE name='blue.txt'; SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); -} { +} [string map [list %perms% $perms] { blue.txt 33188 1000000000 edcba 0 - h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8 i.txt 33188 4 zxcvb 0 -} +}] do_execsql_test 1.6.6 { UPDATE zz SET mode=NULL, data = NULL WHERE name='blue.txt'; SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); -} { +} [string map [list %perms% $perms] { blue.txt/ 16877 1000000000 {} 0 - h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8 i.txt 33188 4 zxcvb 0 -} +}] do_catchsql_test 1.6.7 { UPDATE zz SET data=NULL WHERE name='i.txt' } {1 {zipfile: mode does not match data}} do_execsql_test 1.6.8 { SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); -} { +} [string map [list %perms% $perms] { blue.txt/ 16877 1000000000 {} 0 - h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8 i.txt 33188 4 zxcvb 0 -} +}] -do_execsql_test 1.6.8 { +do_execsql_test 1.6.9 { UPDATE zz SET data = '' WHERE name='i.txt'; SELECT name,mode,mtime,data,method from zipfile('test.zip'); -} { +} [string map [list %perms% $perms] { blue.txt/ 16877 1000000000 {} 0 - h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 + h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8 i.txt 33188 4 {} 0 -} +}] -do_execsql_test 1.6.9 { +do_execsql_test 1.6.10 { SELECT a.name, a.data FROM zz AS a, zz AS b WHERE a.name=+b.name AND +a.mode=b.mode } { blue.txt/ {} h.txt aaaaaaaaaabbbbbbbbbb i.txt {} } -do_execsql_test 1.6.10 { +do_execsql_test 1.6.11 { SELECT name, data FROM zz WHERE name LIKE '%txt' } { h.txt aaaaaaaaaabbbbbbbbbb i.txt {} } @@ -358,17 +394,22 @@ dirname2/ 16877 {} dirname2/file1.txt 33188 abcdefghijklmnop } do_zip_tests 2.4a test.zip -# If on unix, check that the [unzip] utility can unpack our archive. +# Check that the [unzip] utility can unpack our archive. # -if {$::tcl_platform(platform)=="unix"} { +if {[info exists ::UNZIP]} { do_test 2.5.1 { forcedelete dirname forcedelete dirname2 - set rc [catch { exec unzip test.zip > /dev/null } msg] + if {$::tcl_platform(platform)=="unix"} { + set null /dev/null + } else { + set null NUL + } + set rc [catch { exec $::UNZIP test.zip > $null } msg] list $rc $msg } {0 {}} do_test 2.5.2 { file isdir dirname3 } 1 do_test 2.5.3 { file isdir dirname2 } 1 do_test 2.5.4 { file isdir dirname2/file1.txt } 0 @@ -382,10 +423,11 @@ #------------------------------------------------------------------------- reset_db forcedelete test.zip load_static_extension db zipfile +load_static_extension db fileio do_execsql_test 3.0 { CREATE VIRTUAL TABLE temp.x1 USING zipfile('test.zip'); INSERT INTO x1(name, data) VALUES('dir1/', NULL); INSERT INTO x1(name, data) VALUES('file1', '1234'); @@ -451,20 +493,20 @@ SELECT NULL, 'def' ) SELECT zipfile(name,data) FROM c } {1 {first argument to zipfile() must be non-NULL}} -do_catchsql_test 4.7 { +do_catchsql_test 4.8 { WITH c(name,data,method) AS ( SELECT 'a.txt', 'abc', 0 UNION SELECT 'b.txt', 'def', 8 UNION SELECT 'c.txt', 'ghi', 16 ) SELECT zipfile(name,NULL,NULL,data,method) FROM c } {1 {illegal method value: 16}} -do_catchsql_test 4.8 { +do_catchsql_test 4.9 { WITH c(name,data) AS ( SELECT 'a.txt', 'abc' UNION SELECT 'b.txt', 'def' UNION SELECT 'c.txt/', 'ghi' ) @@ -483,13 +525,12 @@ ) } { a.txt 946684800 abc } -if {[info vars ::UNZIP]!=""} { +if {[info exists ::UNZIP]} { ifcapable datetime { - load_static_extension db fileio forcedelete test1.zip test2.zip do_test 6.0 { execsql { WITH c(name,mtime,data) AS ( SELECT 'a.txt', 946684800, 'abc' UNION ALL @@ -500,11 +541,11 @@ writefile('test2.zip', ( zipfile(name, NULL, mtime, data) ) ) FROM c; } forcedelete test_unzip file mkdir test_unzip - exec unzip -d test_unzip test1.zip + exec $::UNZIP -d test_unzip test1.zip db eval { SELECT name, strftime('%s', mtime, 'unixepoch', 'localtime') FROM fsdir('test_unzip') WHERE name!='test_unzip' ORDER BY name @@ -532,11 +573,11 @@ } do_test 6.2 { forcedelete test_unzip file mkdir test_unzip - exec unzip -d test_unzip test2.zip + exec $::UNZIP -d test_unzip test2.zip db eval { SELECT name, mtime FROM fsdir('test_unzip') WHERE name!='test_unzip' ORDER BY name @@ -650,10 +691,12 @@ # catch {db close} forcedelete test.zip test.db sqlite3 db :memory: load_static_extension db zipfile +load_static_extension db fileio + do_execsql_test 10.0 { CREATE VIRTUAL TABLE z USING zipfile('test.zip'); } {} do_catchsql_test 10.1 { INSERT INTO z(name,data) VALUES('a0','one'),('a0','two'); @@ -672,7 +715,46 @@ } {} do_execsql_test 10.6 { SELECT name, data FROM z; } {a0 four} +do_execsql_test 11.1 { + DELETE FROM z; +} {} +do_execsql_test 11.2 { + SELECT name, data FROM z; +} {} +do_execsql_test 11.3 { + INSERT INTO z (name,data) VALUES ('b0','one'); + SELECT name, data FROM z; +} {b0 one} +do_execsql_test 11.4 { + UPDATE z SET name = 'b1' WHERE name = 'b0'; + SELECT name, data FROM z; +} {b1 one} +do_execsql_test 11.5 { + INSERT INTO z (name,data) VALUES ('b0','one'); + SELECT name, data FROM z ORDER BY name; +} {b0 one b1 one} +do_catchsql_test 11.6 { + UPDATE z SET name = 'b1' WHERE name = 'b0'; +} {1 {duplicate name: "b1"}} +do_execsql_test 11.7 { + UPDATE z SET data = 'two' WHERE name = 'b0'; + SELECT name, data FROM z ORDER BY name; +} {b0 two b1 one} +do_catchsql_test 11.8 { + UPDATE z SET name = 'b1'; +} {1 {duplicate name: "b1"}} +do_catchsql_test 11.9 { + UPDATE z SET name = 'b2'; +} {1 {duplicate name: "b2"}} +do_execsql_test 11.10 { + UPDATE z SET name = name; + SELECT name, data FROM z ORDER BY name; +} {b0 two b2 one} +do_execsql_test 11.11 { + UPDATE z SET name = name || 'suffix'; + SELECT name, data FROM z ORDER BY name; +} {b0suffix two b2suffix one} finish_test Index: test/zipfile2.test ================================================================== --- test/zipfile2.test +++ test/zipfile2.test @@ -7,10 +7,12 @@ # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # + +package require Tcl 8.6 set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix zipfile2 @@ -48,16 +50,21 @@ CREATE VIRTUAL TABLE ddd USING zipfile([testzip]); CREATE VIRTUAL TABLE eee USING zipfile(testzip); CREATE VIRTUAL TABLE fff USING zipfile('test''zip'); } +if {$::tcl_platform(platform)=="windows"} { + set res {1 {cannot open file: testdir}} +} else { + set res {1 {error in fread()}} +} do_test 2.0 { forcedelete testdir file mkdir testdir execsql { CREATE VIRTUAL TABLE hhh USING zipfile('testdir') } catchsql { SELECT * FROM hhh } -} {1 {error in fread()}} +} $res set archive { 504B0304140000080000D4A52BEC09F3B6E0110000001100000005000900612E 747874555405000140420F00636F6E74656E7473206F6620612E747874504B03 @@ -201,8 +208,39 @@ set blob2 [binary decode hex $hex] execsql { SELECT name, data IS NULL FROM zipfile($blob2) } } {notadi/ 1} +#------------------------------------------------------------------------- +# Test that duplicate entries may not be created using UPDATE +# statements. +# +forcedelete test.zip +do_execsql_test 6.0 { + CREATE VIRTUAL TABLE temp.zip USING zipfile('test.zip'); + INSERT INTO temp.zip (name,data) VALUES ('test1','test'); + INSERT INTO temp.zip (name,data) VALUES ('test2','test'); +} +do_catchsql_test 6.1 { + UPDATE temp.zip SET name='test1' WHERE name='test2' +} {1 {duplicate name: "test1"}} + +forcedelete test.zip +do_catchsql_test 6.2 { + DROP TABLE zip; + CREATE VIRTUAL TABLE temp.zip USING zipfile('test.zip'); + INSERT INTO temp.zip (name,data) VALUES ('test','test'); + UPDATE temp.zip set name=name||'new' where name='test'; + INSERT INTO temp.zip (name,data) VALUES ('test','test'); + UPDATE temp.zip set name=name||'new' where name='test'; +} {1 {duplicate name: "testnew"}} + +forcedelete test.zip +do_execsql_test 6.3 { + INSERT INTO temp.zip (name,data) VALUES ('test1','test'); + INSERT INTO temp.zip (name,data) VALUES ('test2','test'); + UPDATE OR REPLACE zip SET name='test2' WHERE name='test1'; + SELECT name FROM zip; +} {test2} finish_test