Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch multiplex-enhancements Excluding Merge-Ins
This is equivalent to a diff from def98fd23e to 718f1ad7df
2011-04-01
| ||
18:39 | Merge the multiplexer enhancements back into the trunk. (check-in: 2c125710cb user: drh tags: trunk) | |
14:22 | Removed dependency on sqliteInt.h so that multiplex VFS shim can be compiled as loadable module. (Closed-Leaf check-in: 718f1ad7df user: shaneh tags: multiplex-enhancements) | |
2011-03-31
| ||
15:11 | Enable/disable support. (check-in: b3c6d9aa9e user: shaneh tags: multiplex-enhancements) | |
2011-03-15
| ||
18:35 | Fix an out-of-order variable declaration in shell.c. (check-in: 7257084650 user: drh tags: trunk) | |
04:45 | Allow multiplex file names to be preceeded by prefix of the form ":multiplex:chunksize:maxchunks:" Still work to be done, though it compiles and prefixes are ignored. (check-in: cfa4a2f7ea user: shaneh tags: multiplex-enhancements) | |
02:55 | Fix cut-and-paste typo in debugging print statement in winMutexTry(). (check-in: def98fd23e user: shaneh tags: trunk) | |
2011-03-14
| ||
13:54 | Merge the unix-excl VFS into the trunk. This merge also adds the -vfs option to the command-line shell. (check-in: 3934b004e9 user: drh tags: trunk) | |
Changes to src/test_multiplex.c.
18 18 ** "chunks" such that the total DB file size may exceed the maximum 19 19 ** file size of the underlying file system. 20 20 ** 21 21 */ 22 22 #include "sqlite3.h" 23 23 #include <string.h> 24 24 #include <assert.h> 25 -#include "sqliteInt.h" 25 +#include "test_multiplex.h" 26 + 27 +#ifndef SQLITE_CORE 28 + #define SQLITE_CORE 1 /* Disable the API redefinition in sqlite3ext.h */ 29 +#endif 30 +#include "sqlite3ext.h" 31 + 32 +/* 33 +** These should be defined to be the same as the values in 34 +** sqliteInt.h. They are defined seperately here so that 35 +** the multiplex VFS shim can be built as a loadable 36 +** module. 37 +*/ 38 +#define UNUSED_PARAMETER(x) (void)(x) 39 +#define MAX_PAGE_SIZE 0x10000 40 +#define DEFAULT_SECTOR_SIZE 0x1000 26 41 27 42 /* 28 43 ** For a build without mutexes, no-op the mutex calls. 29 44 */ 30 45 #if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 31 46 #define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) 32 47 #define sqlite3_mutex_free(X) ................................................................................ 35 50 #define sqlite3_mutex_leave(X) 36 51 #define sqlite3_mutex_held(X) ((void)(X),1) 37 52 #define sqlite3_mutex_notheld(X) ((void)(X),1) 38 53 #endif /* SQLITE_THREADSAFE==0 */ 39 54 40 55 41 56 /************************ Shim Definitions ******************************/ 57 + 58 +#define SQLITE_MULTIPLEX_VFS_NAME "multiplex" 42 59 43 60 /* This is the limit on the chunk size. It may be changed by calling 44 -** the sqlite3_multiplex_set() interface. 61 +** the xFileControl() interface. It will be rounded up to a 62 +** multiple of MAX_PAGE_SIZE. We default it here to 1GB. 45 63 */ 46 -#define SQLITE_MULTIPLEX_CHUNK_SIZE 0x40000000 64 +#define SQLITE_MULTIPLEX_CHUNK_SIZE (MAX_PAGE_SIZE*16384) 65 + 47 66 /* Default limit on number of chunks. Care should be taken 48 67 ** so that values for chunks numbers fit in the SQLITE_MULTIPLEX_EXT_FMT 49 68 ** format specifier. It may be changed by calling 50 -** the sqlite3_multiplex_set() interface. 69 +** the xFileControl() interface. 51 70 */ 52 71 #define SQLITE_MULTIPLEX_MAX_CHUNKS 32 53 72 54 73 /* If SQLITE_MULTIPLEX_EXT_OVWR is defined, the 55 74 ** last SQLITE_MULTIPLEX_EXT_SZ characters of the 56 75 ** filename will be overwritten, otherwise, the 57 76 ** multiplex extension is simply appended to the filename. ................................................................................ 78 97 */ 79 98 struct multiplexGroup { 80 99 sqlite3_file **pReal; /* Handles to each chunk */ 81 100 char *bOpen; /* array of bools - 0 if chunk not opened */ 82 101 char *zName; /* Base filename of this group */ 83 102 int nName; /* Length of base filename */ 84 103 int flags; /* Flags used for original opening */ 104 + int nChunkSize; /* Chunk size used for this group */ 105 + int nMaxChunks; /* Max number of chunks for this group */ 106 + int bEnabled; /* TRUE to use Multiplex VFS for this file */ 85 107 multiplexGroup *pNext, *pPrev; /* Doubly linked list of all group objects */ 86 108 }; 87 109 88 110 /* 89 111 ** An instance of the following object represents each open connection 90 112 ** to a file that is multiplex'ed. This object is a 91 113 ** subclass of sqlite3_file. The sqlite3_file object for the underlying ................................................................................ 136 158 */ 137 159 sqlite3_mutex *pMutex; 138 160 139 161 /* List of multiplexGroup objects. 140 162 */ 141 163 multiplexGroup *pGroups; 142 164 143 - /* Chunk params. 144 - */ 145 - int nChunkSize; 146 - int nMaxChunks; 147 - 148 165 /* Storage for temp file names. Allocated during 149 166 ** initialization to the max pathname of the underlying VFS. 150 167 */ 151 168 char *zName; 152 169 153 170 } gMultiplex; 154 171 ................................................................................ 155 172 /************************* Utility Routines *********************************/ 156 173 /* 157 174 ** Acquire and release the mutex used to serialize access to the 158 175 ** list of multiplexGroups. 159 176 */ 160 177 static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); } 161 178 static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); } 179 + 180 +/* 181 +** Compute a string length that is limited to what can be stored in 182 +** lower 30 bits of a 32-bit signed integer. 183 +** 184 +** The value returned will never be negative. Nor will it ever be greater 185 +** than the actual length of the string. For very long strings (greater 186 +** than 1GiB) the value returned might be less than the true string length. 187 +*/ 188 +int multiplexStrlen30(const char *z){ 189 + const char *z2 = z; 190 + if( z==0 ) return 0; 191 + while( *z2 ){ z2++; } 192 + return 0x3fffffff & (int)(z2 - z); 193 +} 162 194 163 195 /* Translate an sqlite3_file* that is really a multiplexGroup* into 164 196 ** the sqlite3_file* for the underlying original VFS. 165 197 */ 166 198 static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc, int *pOutFlags){ 167 199 multiplexGroup *pGroup = pConn->pGroup; 168 200 sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ 169 - if( iChunk<gMultiplex.nMaxChunks ){ 201 + if( iChunk<pGroup->nMaxChunks ){ 170 202 sqlite3_file *pSubOpen = pGroup->pReal[iChunk]; /* Real file descriptor */ 171 203 if( !pGroup->bOpen[iChunk] ){ 172 204 memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); 173 205 if( iChunk ){ 174 206 #ifdef SQLITE_MULTIPLEX_EXT_OVWR 175 207 sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, iChunk); 176 208 #else ................................................................................ 186 218 } 187 219 *rc = SQLITE_OK; 188 220 return pSubOpen; 189 221 } 190 222 *rc = SQLITE_FULL; 191 223 return NULL; 192 224 } 225 + 226 +/* 227 +** This is the implementation of the multiplex_control() SQL function. 228 +*/ 229 +static void multiplexControlFunc( 230 + sqlite3_context *context, 231 + int argc, 232 + sqlite3_value **argv 233 +){ 234 + int rc = SQLITE_OK; 235 + sqlite3 *db = sqlite3_context_db_handle(context); 236 + int op; 237 + int iVal; 238 + 239 + if( !db || argc!=2 ){ 240 + rc = SQLITE_ERROR; 241 + }else{ 242 + /* extract params */ 243 + op = sqlite3_value_int(argv[0]); 244 + iVal = sqlite3_value_int(argv[1]); 245 + /* map function op to file_control op */ 246 + switch( op ){ 247 + case 1: 248 + op = MULTIPLEX_CTRL_ENABLE; 249 + break; 250 + case 2: 251 + op = MULTIPLEX_CTRL_SET_CHUNK_SIZE; 252 + break; 253 + case 3: 254 + op = MULTIPLEX_CTRL_SET_MAX_CHUNKS; 255 + break; 256 + default: 257 + rc = SQLITE_NOTFOUND; 258 + break; 259 + } 260 + } 261 + if( rc==SQLITE_OK ){ 262 + rc = sqlite3_file_control(db, 0, op, &iVal); 263 + } 264 + sqlite3_result_error_code(context, rc); 265 +} 266 + 267 +/* 268 +** This is the entry point to register the auto-extension for the 269 +** multiplex_control() function. 270 +*/ 271 +static int multiplexFuncInit( 272 + sqlite3 *db, 273 + char **pzErrMsg, 274 + const sqlite3_api_routines *pApi 275 +){ 276 + int rc; 277 + rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY, 278 + 0, multiplexControlFunc, 0, 0); 279 + return rc; 280 +} 193 281 194 282 /************************* VFS Method Wrappers *****************************/ 195 283 196 284 /* 197 285 ** This is the xOpen method used for the "multiplex" VFS. 198 286 ** 199 287 ** Most of the work is done by the underlying original VFS. This method ................................................................................ 208 296 int *pOutFlags /* Flags showing results of opening */ 209 297 ){ 210 298 int rc; /* Result code */ 211 299 multiplexConn *pMultiplexOpen; /* The new multiplex file descriptor */ 212 300 multiplexGroup *pGroup; /* Corresponding multiplexGroup object */ 213 301 sqlite3_file *pSubOpen; /* Real file descriptor */ 214 302 sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ 215 - int nName = sqlite3Strlen30(zName); 303 + int nName = multiplexStrlen30(zName); 216 304 int i; 217 305 int sz; 218 306 219 307 UNUSED_PARAMETER(pVfs); 220 308 221 309 /* We need to create a group structure and manage 222 310 ** access to this group of files. 223 311 */ 224 312 multiplexEnter(); 225 313 pMultiplexOpen = (multiplexConn*)pConn; 226 314 /* allocate space for group */ 227 - sz = sizeof(multiplexGroup) /* multiplexGroup */ 228 - + (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks) /* pReal[] */ 229 - + (pOrigVfs->szOsFile*gMultiplex.nMaxChunks) /* *pReal */ 230 - + gMultiplex.nMaxChunks /* bOpen[] */ 231 - + nName + 1; /* zName */ 315 + sz = sizeof(multiplexGroup) /* multiplexGroup */ 316 + + (sizeof(sqlite3_file *)*SQLITE_MULTIPLEX_MAX_CHUNKS) /* pReal[] */ 317 + + (pOrigVfs->szOsFile*SQLITE_MULTIPLEX_MAX_CHUNKS) /* *pReal */ 318 + + SQLITE_MULTIPLEX_MAX_CHUNKS /* bOpen[] */ 319 + + nName + 1; /* zName */ 232 320 #ifndef SQLITE_MULTIPLEX_EXT_OVWR 233 321 sz += SQLITE_MULTIPLEX_EXT_SZ; 234 322 assert(nName+SQLITE_MULTIPLEX_EXT_SZ < pOrigVfs->mxPathname); 235 323 #else 236 324 assert(nName >= SQLITE_MULTIPLEX_EXT_SZ); 237 325 assert(nName < pOrigVfs->mxPathname); 238 326 #endif ................................................................................ 240 328 if( pGroup==0 ){ 241 329 rc=SQLITE_NOMEM; 242 330 }else{ 243 331 /* assign pointers to extra space allocated */ 244 332 char *p = (char *)&pGroup[1]; 245 333 pMultiplexOpen->pGroup = pGroup; 246 334 memset(pGroup, 0, sz); 335 + pGroup->bEnabled = -1; 336 + pGroup->nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE; 337 + pGroup->nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS; 247 338 pGroup->pReal = (sqlite3_file **)p; 248 - p += (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks); 249 - for(i=0; i<gMultiplex.nMaxChunks; i++){ 339 + p += (sizeof(sqlite3_file *)*pGroup->nMaxChunks); 340 + for(i=0; i<pGroup->nMaxChunks; i++){ 250 341 pGroup->pReal[i] = (sqlite3_file *)p; 251 342 p += pOrigVfs->szOsFile; 252 343 } 344 + /* bOpen[] vals should all be zero from memset above */ 253 345 pGroup->bOpen = p; 254 - p += gMultiplex.nMaxChunks; 346 + p += pGroup->nMaxChunks; 255 347 pGroup->zName = p; 256 348 /* save off base filename, name length, and original open flags */ 257 349 memcpy(pGroup->zName, zName, nName+1); 258 350 pGroup->nName = nName; 259 351 pGroup->flags = flags; 260 352 pSubOpen = multiplexSubOpen(pMultiplexOpen, 0, &rc, pOutFlags); 261 353 if( pSubOpen ){ 354 + /* if this file is already larger than chunk size, disable 355 + ** the multiplex feature. 356 + */ 357 + sqlite3_int64 sz; 358 + int rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); 359 + if( (rc2==SQLITE_OK) && (sz>pGroup->nChunkSize) ){ 360 + pGroup->bEnabled = 0; 361 + } 262 362 if( pSubOpen->pMethods->iVersion==1 ){ 263 363 pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1; 264 364 }else{ 265 365 pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV2; 266 366 } 267 367 /* place this group at the head of our list */ 268 368 pGroup->pNext = gMultiplex.pGroups; ................................................................................ 284 384 static int multiplexDelete( 285 385 sqlite3_vfs *pVfs, /* The multiplex VFS */ 286 386 const char *zName, /* Name of file to delete */ 287 387 int syncDir 288 388 ){ 289 389 sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ 290 390 int rc = SQLITE_OK; 291 - int nName = sqlite3Strlen30(zName); 391 + int nName = multiplexStrlen30(zName); 292 392 int i; 293 393 294 394 UNUSED_PARAMETER(pVfs); 295 395 296 396 multiplexEnter(); 297 397 memcpy(gMultiplex.zName, zName, nName+1); 298 - for(i=0; i<gMultiplex.nMaxChunks; i++){ 398 + for(i=0; i<SQLITE_MULTIPLEX_MAX_CHUNKS; i++){ 299 399 int rc2; 300 400 int exists = 0; 301 401 if( i ){ 302 402 #ifdef SQLITE_MULTIPLEX_EXT_OVWR 303 - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i); 403 + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, 404 + gMultiplex.zName+nName-SQLITE_MULTIPLEX_EXT_SZ, 405 + SQLITE_MULTIPLEX_EXT_FMT, i); 304 406 #else 305 - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+nName, SQLITE_MULTIPLEX_EXT_FMT, i); 407 + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, 408 + gMultiplex.zName+nName, 409 + SQLITE_MULTIPLEX_EXT_FMT, i); 306 410 #endif 307 411 } 308 - rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists); 412 + rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, 413 + SQLITE_ACCESS_EXISTS, &exists); 309 414 if( rc2==SQLITE_OK && exists){ 310 415 /* if it exists, delete it */ 311 416 rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, syncDir); 312 417 if( rc2!=SQLITE_OK ) rc = rc2; 313 418 }else{ 314 419 /* stop at first "gap" */ 315 420 break; ................................................................................ 363 468 static int multiplexClose(sqlite3_file *pConn){ 364 469 multiplexConn *p = (multiplexConn*)pConn; 365 470 multiplexGroup *pGroup = p->pGroup; 366 471 int rc = SQLITE_OK; 367 472 int i; 368 473 multiplexEnter(); 369 474 /* close any open handles */ 370 - for(i=0; i<gMultiplex.nMaxChunks; i++){ 475 + for(i=0; i<pGroup->nMaxChunks; i++){ 371 476 if( pGroup->bOpen[i] ){ 372 477 sqlite3_file *pSubOpen = pGroup->pReal[i]; 373 478 int rc2 = pSubOpen->pMethods->xClose(pSubOpen); 374 479 if( rc2!=SQLITE_OK ) rc = rc2; 375 480 pGroup->bOpen[i] = 0; 376 481 } 377 482 } ................................................................................ 394 499 static int multiplexRead( 395 500 sqlite3_file *pConn, 396 501 void *pBuf, 397 502 int iAmt, 398 503 sqlite3_int64 iOfst 399 504 ){ 400 505 multiplexConn *p = (multiplexConn*)pConn; 506 + multiplexGroup *pGroup = p->pGroup; 401 507 int rc = SQLITE_OK; 402 508 multiplexEnter(); 403 - while( iAmt > 0 ){ 404 - int i = (int)(iOfst/gMultiplex.nChunkSize); 405 - sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); 406 - if( pSubOpen ){ 407 - int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize; 408 - if( extra<0 ) extra = 0; 409 - iAmt -= extra; 410 - rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize); 411 - if( rc!=SQLITE_OK ) break; 412 - pBuf = (char *)pBuf + iAmt; 413 - iOfst += iAmt; 414 - iAmt = extra; 415 - }else{ 416 - rc = SQLITE_IOERR_READ; 417 - break; 509 + if( !pGroup->bEnabled ){ 510 + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); 511 + rc = ( !pSubOpen ) ? SQLITE_IOERR_READ : pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst); 512 + }else{ 513 + while( iAmt > 0 ){ 514 + int i = (int)(iOfst / pGroup->nChunkSize); 515 + sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); 516 + if( pSubOpen ){ 517 + int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize; 518 + if( extra<0 ) extra = 0; 519 + iAmt -= extra; 520 + rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize); 521 + if( rc!=SQLITE_OK ) break; 522 + pBuf = (char *)pBuf + iAmt; 523 + iOfst += iAmt; 524 + iAmt = extra; 525 + }else{ 526 + rc = SQLITE_IOERR_READ; 527 + break; 528 + } 418 529 } 419 530 } 420 531 multiplexLeave(); 421 532 return rc; 422 533 } 423 534 424 535 /* Pass xWrite requests thru to the original VFS after ................................................................................ 428 539 static int multiplexWrite( 429 540 sqlite3_file *pConn, 430 541 const void *pBuf, 431 542 int iAmt, 432 543 sqlite3_int64 iOfst 433 544 ){ 434 545 multiplexConn *p = (multiplexConn*)pConn; 546 + multiplexGroup *pGroup = p->pGroup; 435 547 int rc = SQLITE_OK; 436 548 multiplexEnter(); 437 - while( iAmt > 0 ){ 438 - int i = (int)(iOfst/gMultiplex.nChunkSize); 439 - sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); 440 - if( pSubOpen ){ 441 - int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize; 442 - if( extra<0 ) extra = 0; 443 - iAmt -= extra; 444 - rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize); 445 - if( rc!=SQLITE_OK ) break; 446 - pBuf = (char *)pBuf + iAmt; 447 - iOfst += iAmt; 448 - iAmt = extra; 449 - }else{ 450 - rc = SQLITE_IOERR_WRITE; 451 - break; 549 + if( !pGroup->bEnabled ){ 550 + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); 551 + rc = ( !pSubOpen ) ? SQLITE_IOERR_WRITE : pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst); 552 + }else{ 553 + while( iAmt > 0 ){ 554 + int i = (int)(iOfst / pGroup->nChunkSize); 555 + sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); 556 + if( pSubOpen ){ 557 + int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize; 558 + if( extra<0 ) extra = 0; 559 + iAmt -= extra; 560 + rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize); 561 + if( rc!=SQLITE_OK ) break; 562 + pBuf = (char *)pBuf + iAmt; 563 + iOfst += iAmt; 564 + iAmt = extra; 565 + }else{ 566 + rc = SQLITE_IOERR_WRITE; 567 + break; 568 + } 452 569 } 453 570 } 454 571 multiplexLeave(); 455 572 return rc; 456 573 } 457 574 458 575 /* Pass xTruncate requests thru to the original VFS after ................................................................................ 459 576 ** determining the correct chunk to operate on. Delete any 460 577 ** chunks above the truncate mark. 461 578 */ 462 579 static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){ 463 580 multiplexConn *p = (multiplexConn*)pConn; 464 581 multiplexGroup *pGroup = p->pGroup; 465 582 int rc = SQLITE_OK; 466 - int rc2; 467 - int i; 468 - sqlite3_file *pSubOpen; 469 - sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ 470 583 multiplexEnter(); 471 - memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); 472 - /* delete the chunks above the truncate limit */ 473 - for(i=(int)(size/gMultiplex.nChunkSize)+1; i<gMultiplex.nMaxChunks; i++){ 474 - /* close any open chunks before deleting them */ 475 - if( pGroup->bOpen[i] ){ 476 - pSubOpen = pGroup->pReal[i]; 477 - rc2 = pSubOpen->pMethods->xClose(pSubOpen); 478 - if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; 479 - pGroup->bOpen[i] = 0; 480 - } 584 + if( !pGroup->bEnabled ){ 585 + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); 586 + rc = ( !pSubOpen ) ? SQLITE_IOERR_TRUNCATE : pSubOpen->pMethods->xTruncate(pSubOpen, size); 587 + }else{ 588 + int rc2; 589 + int i; 590 + sqlite3_file *pSubOpen; 591 + sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ 592 + memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); 593 + /* delete the chunks above the truncate limit */ 594 + for(i=(int)(size / pGroup->nChunkSize)+1; i<pGroup->nMaxChunks; i++){ 595 + /* close any open chunks before deleting them */ 596 + if( pGroup->bOpen[i] ){ 597 + pSubOpen = pGroup->pReal[i]; 598 + rc2 = pSubOpen->pMethods->xClose(pSubOpen); 599 + if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; 600 + pGroup->bOpen[i] = 0; 601 + } 481 602 #ifdef SQLITE_MULTIPLEX_EXT_OVWR 482 - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i); 603 + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, 604 + gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, 605 + SQLITE_MULTIPLEX_EXT_FMT, i); 483 606 #else 484 - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i); 607 + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, 608 + gMultiplex.zName+pGroup->nName, 609 + SQLITE_MULTIPLEX_EXT_FMT, i); 485 610 #endif 486 - rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0); 487 - if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; 488 - } 489 - pSubOpen = multiplexSubOpen(p, (int)(size/gMultiplex.nChunkSize), &rc2, NULL); 490 - if( pSubOpen ){ 491 - rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size%gMultiplex.nChunkSize); 492 - if( rc2!=SQLITE_OK ) rc = rc2; 493 - }else{ 494 - rc = SQLITE_IOERR_TRUNCATE; 611 + rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0); 612 + if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; 613 + } 614 + pSubOpen = multiplexSubOpen(p, (int)(size / pGroup->nChunkSize), &rc2, NULL); 615 + if( pSubOpen ){ 616 + rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->nChunkSize); 617 + if( rc2!=SQLITE_OK ) rc = rc2; 618 + }else{ 619 + rc = SQLITE_IOERR_TRUNCATE; 620 + } 495 621 } 496 622 multiplexLeave(); 497 623 return rc; 498 624 } 499 625 500 626 /* Pass xSync requests through to the original VFS without change 501 627 */ 502 628 static int multiplexSync(sqlite3_file *pConn, int flags){ 503 629 multiplexConn *p = (multiplexConn*)pConn; 504 630 multiplexGroup *pGroup = p->pGroup; 505 631 int rc = SQLITE_OK; 506 632 int i; 507 633 multiplexEnter(); 508 - for(i=0; i<gMultiplex.nMaxChunks; i++){ 634 + for(i=0; i<pGroup->nMaxChunks; i++){ 509 635 /* if we don't have it open, we don't need to sync it */ 510 636 if( pGroup->bOpen[i] ){ 511 637 sqlite3_file *pSubOpen = pGroup->pReal[i]; 512 638 int rc2 = pSubOpen->pMethods->xSync(pSubOpen, flags); 513 639 if( rc2!=SQLITE_OK ) rc = rc2; 514 640 } 515 641 } ................................................................................ 523 649 static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ 524 650 multiplexConn *p = (multiplexConn*)pConn; 525 651 multiplexGroup *pGroup = p->pGroup; 526 652 int rc = SQLITE_OK; 527 653 int rc2; 528 654 int i; 529 655 multiplexEnter(); 530 - *pSize = 0; 531 - for(i=0; i<gMultiplex.nMaxChunks; i++){ 532 - sqlite3_file *pSubOpen = NULL; 533 - /* if not opened already, check to see if the chunk exists */ 534 - if( pGroup->bOpen[i] ){ 535 - pSubOpen = pGroup->pReal[i]; 536 - }else{ 537 - sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ 538 - int exists = 0; 539 - memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); 540 - if( i ){ 656 + if( !pGroup->bEnabled ){ 657 + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); 658 + rc = ( !pSubOpen ) ? SQLITE_IOERR_FSTAT : pSubOpen->pMethods->xFileSize(pSubOpen, pSize); 659 + }else{ 660 + *pSize = 0; 661 + for(i=0; i<pGroup->nMaxChunks; i++){ 662 + sqlite3_file *pSubOpen = NULL; 663 + /* if not opened already, check to see if the chunk exists */ 664 + if( pGroup->bOpen[i] ){ 665 + pSubOpen = pGroup->pReal[i]; 666 + }else{ 667 + sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ 668 + int exists = 0; 669 + memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); 670 + if( i ){ 541 671 #ifdef SQLITE_MULTIPLEX_EXT_OVWR 542 - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i); 672 + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, 673 + gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, 674 + SQLITE_MULTIPLEX_EXT_FMT, i); 543 675 #else 544 - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i); 676 + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, 677 + gMultiplex.zName+pGroup->nName, 678 + SQLITE_MULTIPLEX_EXT_FMT, i); 545 679 #endif 680 + } 681 + rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, 682 + SQLITE_ACCESS_EXISTS, &exists); 683 + if( rc2==SQLITE_OK && exists){ 684 + /* if it exists, open it */ 685 + pSubOpen = multiplexSubOpen(p, i, &rc, NULL); 686 + }else{ 687 + /* stop at first "gap" */ 688 + break; 689 + } 546 690 } 547 - rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists); 548 - if( rc2==SQLITE_OK && exists){ 549 - /* if it exists, open it */ 550 - pSubOpen = multiplexSubOpen(p, i, &rc, NULL); 691 + if( pSubOpen ){ 692 + sqlite3_int64 sz; 693 + rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); 694 + if( rc2!=SQLITE_OK ){ 695 + rc = rc2; 696 + }else{ 697 + if( sz>pGroup->nChunkSize ){ 698 + rc = SQLITE_IOERR_FSTAT; 699 + } 700 + *pSize += sz; 701 + } 551 702 }else{ 552 - /* stop at first "gap" */ 553 703 break; 554 704 } 555 705 } 556 - if( pSubOpen ){ 557 - sqlite3_int64 sz; 558 - rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); 559 - if( rc2!=SQLITE_OK ){ 560 - rc = rc2; 561 - }else{ 562 - if( sz>gMultiplex.nChunkSize ){ 563 - rc = SQLITE_IOERR_FSTAT; 564 - } 565 - *pSize += sz; 566 - } 567 - }else{ 568 - break; 569 - } 570 706 } 571 707 multiplexLeave(); 572 708 return rc; 573 709 } 574 710 575 711 /* Pass xLock requests through to the original VFS unchanged. 576 712 */ ................................................................................ 604 740 sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); 605 741 if( pSubOpen ){ 606 742 return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut); 607 743 } 608 744 return SQLITE_IOERR_CHECKRESERVEDLOCK; 609 745 } 610 746 611 -/* Pass xFileControl requests through to the original VFS unchanged. 747 +/* Pass xFileControl requests through to the original VFS unchanged, 748 +** except for any MULTIPLEX_CTRL_* requests here. 612 749 */ 613 750 static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){ 614 751 multiplexConn *p = (multiplexConn*)pConn; 615 - int rc; 752 + multiplexGroup *pGroup = p->pGroup; 753 + int rc = SQLITE_ERROR; 616 754 sqlite3_file *pSubOpen; 617 - if ( op==SQLITE_FCNTL_SIZE_HINT || op==SQLITE_FCNTL_CHUNK_SIZE ) return SQLITE_OK; 618 - pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); 619 - if( pSubOpen ){ 620 - return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); 755 + 756 + if( !gMultiplex.isInitialized ) return SQLITE_MISUSE; 757 + switch( op ){ 758 + case MULTIPLEX_CTRL_ENABLE: 759 + if( pArg ) { 760 + int bEnabled = *(int *)pArg; 761 + pGroup->bEnabled = bEnabled; 762 + rc = SQLITE_OK; 763 + } 764 + break; 765 + case MULTIPLEX_CTRL_SET_CHUNK_SIZE: 766 + if( pArg ) { 767 + int nChunkSize = *(int *)pArg; 768 + if( nChunkSize<1 ){ 769 + rc = SQLITE_MISUSE; 770 + }else{ 771 + /* Round up to nearest multiple of MAX_PAGE_SIZE. */ 772 + nChunkSize = (nChunkSize + (MAX_PAGE_SIZE-1)); 773 + nChunkSize &= ~(MAX_PAGE_SIZE-1); 774 + pGroup->nChunkSize = nChunkSize; 775 + rc = SQLITE_OK; 776 + } 777 + } 778 + break; 779 + case MULTIPLEX_CTRL_SET_MAX_CHUNKS: 780 + if( pArg ) { 781 + int nMaxChunks = *(int *)pArg; 782 + if(( nMaxChunks<1 ) || ( nMaxChunks>SQLITE_MULTIPLEX_MAX_CHUNKS )){ 783 + rc = SQLITE_MISUSE; 784 + }else{ 785 + pGroup->nMaxChunks = nMaxChunks; 786 + rc = SQLITE_OK; 787 + } 788 + } 789 + break; 790 + case SQLITE_FCNTL_SIZE_HINT: 791 + case SQLITE_FCNTL_CHUNK_SIZE: 792 + /* no-op these */ 793 + rc = SQLITE_OK; 794 + break; 795 + default: 796 + pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); 797 + if( pSubOpen ){ 798 + rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); 799 + } 800 + break; 621 801 } 622 - return SQLITE_ERROR; 802 + return rc; 623 803 } 624 804 625 805 /* Pass xSectorSize requests through to the original VFS unchanged. 626 806 */ 627 807 static int multiplexSectorSize(sqlite3_file *pConn){ 628 808 multiplexConn *p = (multiplexConn*)pConn; 629 809 int rc; 630 810 sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); 631 811 if( pSubOpen ){ 632 812 return pSubOpen->pMethods->xSectorSize(pSubOpen); 633 813 } 634 - return SQLITE_DEFAULT_SECTOR_SIZE; 814 + return DEFAULT_SECTOR_SIZE; 635 815 } 636 816 637 817 /* Pass xDeviceCharacteristics requests through to the original VFS unchanged. 638 818 */ 639 819 static int multiplexDeviceCharacteristics(sqlite3_file *pConn){ 640 820 multiplexConn *p = (multiplexConn*)pConn; 641 821 int rc; ................................................................................ 702 882 return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag); 703 883 } 704 884 return SQLITE_OK; 705 885 } 706 886 707 887 /************************** Public Interfaces *****************************/ 708 888 /* 709 -** Initialize the multiplex VFS shim. Use the VFS named zOrigVfsName 710 -** as the VFS that does the actual work. Use the default if 711 -** zOrigVfsName==NULL. 889 +** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize() 890 +** 891 +** Use the VFS named zOrigVfsName as the VFS that does the actual work. 892 +** Use the default if zOrigVfsName==NULL. 712 893 ** 713 894 ** The multiplex VFS shim is named "multiplex". It will become the default 714 895 ** VFS if makeDefault is non-zero. 715 896 ** 716 897 ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once 717 898 ** during start-up. 718 899 */ ................................................................................ 727 908 return SQLITE_NOMEM; 728 909 } 729 910 gMultiplex.zName = sqlite3_malloc(pOrigVfs->mxPathname); 730 911 if( !gMultiplex.zName ){ 731 912 sqlite3_mutex_free(gMultiplex.pMutex); 732 913 return SQLITE_NOMEM; 733 914 } 734 - gMultiplex.nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE; 735 - gMultiplex.nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS; 736 915 gMultiplex.pGroups = NULL; 737 916 gMultiplex.isInitialized = 1; 738 917 gMultiplex.pOrigVfs = pOrigVfs; 739 918 gMultiplex.sThisVfs = *pOrigVfs; 740 919 gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn); 741 - gMultiplex.sThisVfs.zName = "multiplex"; 920 + gMultiplex.sThisVfs.zName = SQLITE_MULTIPLEX_VFS_NAME; 742 921 gMultiplex.sThisVfs.xOpen = multiplexOpen; 743 922 gMultiplex.sThisVfs.xDelete = multiplexDelete; 744 923 gMultiplex.sThisVfs.xAccess = multiplexAccess; 745 924 gMultiplex.sThisVfs.xFullPathname = multiplexFullPathname; 746 925 gMultiplex.sThisVfs.xDlOpen = multiplexDlOpen; 747 926 gMultiplex.sThisVfs.xDlError = multiplexDlError; 748 927 gMultiplex.sThisVfs.xDlSym = multiplexDlSym; ................................................................................ 769 948 gMultiplex.sIoMethodsV2 = gMultiplex.sIoMethodsV1; 770 949 gMultiplex.sIoMethodsV2.iVersion = 2; 771 950 gMultiplex.sIoMethodsV2.xShmMap = multiplexShmMap; 772 951 gMultiplex.sIoMethodsV2.xShmLock = multiplexShmLock; 773 952 gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier; 774 953 gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap; 775 954 sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault); 955 + 956 + sqlite3_auto_extension((void*)multiplexFuncInit); 957 + 776 958 return SQLITE_OK; 777 959 } 778 960 779 961 /* 780 -** Shutdown the multiplex system. 962 +** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown() 781 963 ** 782 964 ** All SQLite database connections must be closed before calling this 783 965 ** routine. 784 966 ** 785 967 ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while 786 968 ** shutting down in order to free all remaining multiplex groups. 787 969 */ ................................................................................ 792 974 sqlite3_free(gMultiplex.zName); 793 975 sqlite3_mutex_free(gMultiplex.pMutex); 794 976 sqlite3_vfs_unregister(&gMultiplex.sThisVfs); 795 977 memset(&gMultiplex, 0, sizeof(gMultiplex)); 796 978 return SQLITE_OK; 797 979 } 798 980 799 -/* 800 -** Adjust chunking params. VFS should be initialized first. 801 -** No files should be open. Re-intializing will reset these 802 -** to the default. 803 -*/ 804 -int sqlite3_multiplex_set( 805 - int nChunkSize, /* Max chunk size */ 806 - int nMaxChunks /* Max number of chunks */ 807 -){ 808 - if( !gMultiplex.isInitialized ) return SQLITE_MISUSE; 809 - if( gMultiplex.pGroups ) return SQLITE_MISUSE; 810 - if( nChunkSize<32 ) return SQLITE_MISUSE; 811 - if( nMaxChunks<1 ) return SQLITE_MISUSE; 812 - if( nMaxChunks>99 ) return SQLITE_MISUSE; 813 - multiplexEnter(); 814 - gMultiplex.nChunkSize = nChunkSize; 815 - gMultiplex.nMaxChunks = nMaxChunks; 816 - multiplexLeave(); 817 - return SQLITE_OK; 818 -} 819 - 820 981 /***************************** Test Code ***********************************/ 821 982 #ifdef SQLITE_TEST 822 983 #include <tcl.h> 823 - 824 984 extern const char *sqlite3TestErrorName(int); 825 985 826 986 827 987 /* 828 988 ** tclcmd: sqlite3_multiplex_initialize NAME MAKEDEFAULT 829 989 */ 830 990 static int test_multiplex_initialize( ................................................................................ 876 1036 /* Call sqlite3_multiplex_shutdown() */ 877 1037 rc = sqlite3_multiplex_shutdown(); 878 1038 Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 879 1039 880 1040 return TCL_OK; 881 1041 } 882 1042 883 -/* 884 -** tclcmd: sqlite3_multiplex_set CHUNK_SIZE MAX_CHUNKS 885 -*/ 886 -static int test_multiplex_set( 887 - void * clientData, 888 - Tcl_Interp *interp, 889 - int objc, 890 - Tcl_Obj *CONST objv[] 891 -){ 892 - int nChunkSize; /* Max chunk size */ 893 - int nMaxChunks; /* Max number of chunks */ 894 - int rc; /* Value returned by sqlite3_multiplex_set() */ 895 - 896 - UNUSED_PARAMETER(clientData); 897 - 898 - /* Process arguments */ 899 - if( objc!=3 ){ 900 - Tcl_WrongNumArgs(interp, 1, objv, "CHUNK_SIZE MAX_CHUNKS"); 901 - return TCL_ERROR; 902 - } 903 - if( Tcl_GetIntFromObj(interp, objv[1], &nChunkSize) ) return TCL_ERROR; 904 - if( Tcl_GetIntFromObj(interp, objv[2], &nMaxChunks) ) return TCL_ERROR; 905 - 906 - /* Invoke sqlite3_multiplex_set() */ 907 - rc = sqlite3_multiplex_set(nChunkSize, nMaxChunks); 908 - 909 - Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 910 - return TCL_OK; 911 -} 912 - 913 1043 /* 914 1044 ** tclcmd: sqlite3_multiplex_dump 915 1045 */ 916 1046 static int test_multiplex_dump( 917 1047 void * clientData, 918 1048 Tcl_Interp *interp, 919 1049 int objc, ................................................................................ 939 1069 Tcl_NewStringObj(pGroup->zName, -1)); 940 1070 Tcl_ListObjAppendElement(interp, pGroupTerm, 941 1071 Tcl_NewIntObj(pGroup->nName)); 942 1072 Tcl_ListObjAppendElement(interp, pGroupTerm, 943 1073 Tcl_NewIntObj(pGroup->flags)); 944 1074 945 1075 /* count number of chunks with open handles */ 946 - for(i=0; i<gMultiplex.nMaxChunks; i++){ 1076 + for(i=0; i<pGroup->nMaxChunks; i++){ 947 1077 if( pGroup->bOpen[i] ) nChunks++; 948 1078 } 949 1079 Tcl_ListObjAppendElement(interp, pGroupTerm, 950 1080 Tcl_NewIntObj(nChunks)); 951 1081 952 1082 Tcl_ListObjAppendElement(interp, pGroupTerm, 953 - Tcl_NewIntObj(gMultiplex.nChunkSize)); 1083 + Tcl_NewIntObj(pGroup->nChunkSize)); 954 1084 Tcl_ListObjAppendElement(interp, pGroupTerm, 955 - Tcl_NewIntObj(gMultiplex.nMaxChunks)); 1085 + Tcl_NewIntObj(pGroup->nMaxChunks)); 956 1086 957 1087 Tcl_ListObjAppendElement(interp, pResult, pGroupTerm); 958 1088 } 959 1089 multiplexLeave(); 960 1090 Tcl_SetObjResult(interp, pResult); 961 1091 return TCL_OK; 962 1092 } 1093 + 1094 +/* 1095 +** Tclcmd: test_multiplex_control HANDLE DBNAME SUB-COMMAND ?INT-VALUE? 1096 +*/ 1097 +static int test_multiplex_control( 1098 + ClientData cd, 1099 + Tcl_Interp *interp, 1100 + int objc, 1101 + Tcl_Obj *CONST objv[] 1102 +){ 1103 + int rc; /* Return code from file_control() */ 1104 + int idx; /* Index in aSub[] */ 1105 + Tcl_CmdInfo cmdInfo; /* Command info structure for HANDLE */ 1106 + sqlite3 *db; /* Underlying db handle for HANDLE */ 1107 + int iValue = 0; 1108 + void *pArg = 0; 1109 + 1110 + struct SubCommand { 1111 + const char *zName; 1112 + int op; 1113 + int argtype; 1114 + } aSub[] = { 1115 + { "enable", MULTIPLEX_CTRL_ENABLE, 1 }, 1116 + { "chunk_size", MULTIPLEX_CTRL_SET_CHUNK_SIZE, 1 }, 1117 + { "max_chunks", MULTIPLEX_CTRL_SET_MAX_CHUNKS, 1 }, 1118 + { 0, 0, 0 } 1119 + }; 1120 + 1121 + if( objc!=5 ){ 1122 + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE DBNAME SUB-COMMAND INT-VALUE"); 1123 + return TCL_ERROR; 1124 + } 1125 + 1126 + if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){ 1127 + Tcl_AppendResult(interp, "expected database handle, got \"", 0); 1128 + Tcl_AppendResult(interp, Tcl_GetString(objv[1]), "\"", 0); 1129 + return TCL_ERROR; 1130 + }else{ 1131 + db = *(sqlite3 **)cmdInfo.objClientData; 1132 + } 1133 + 1134 + rc = Tcl_GetIndexFromObjStruct( 1135 + interp, objv[3], aSub, sizeof(aSub[0]), "sub-command", 0, &idx 1136 + ); 1137 + if( rc!=TCL_OK ) return rc; 1138 + 1139 + switch( aSub[idx].argtype ){ 1140 + case 1: 1141 + if( Tcl_GetIntFromObj(interp, objv[4], &iValue) ){ 1142 + return TCL_ERROR; 1143 + } 1144 + pArg = (void *)&iValue; 1145 + break; 1146 + default: 1147 + Tcl_WrongNumArgs(interp, 4, objv, "SUB-COMMAND"); 1148 + return TCL_ERROR; 1149 + } 1150 + 1151 + rc = sqlite3_file_control(db, Tcl_GetString(objv[2]), aSub[idx].op, pArg); 1152 + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); 1153 + return (rc==SQLITE_OK) ? TCL_OK : TCL_ERROR; 1154 +} 963 1155 964 1156 /* 965 1157 ** This routine registers the custom TCL commands defined in this 966 1158 ** module. This should be the only procedure visible from outside 967 1159 ** of this module. 968 1160 */ 969 1161 int Sqlitemultiplex_Init(Tcl_Interp *interp){ 970 1162 static struct { 971 1163 char *zName; 972 1164 Tcl_ObjCmdProc *xProc; 973 1165 } aCmd[] = { 974 1166 { "sqlite3_multiplex_initialize", test_multiplex_initialize }, 975 1167 { "sqlite3_multiplex_shutdown", test_multiplex_shutdown }, 976 - { "sqlite3_multiplex_set", test_multiplex_set }, 977 1168 { "sqlite3_multiplex_dump", test_multiplex_dump }, 1169 + { "sqlite3_multiplex_control", test_multiplex_control }, 978 1170 }; 979 1171 int i; 980 1172 981 1173 for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){ 982 1174 Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0); 983 1175 } 984 1176 985 1177 return TCL_OK; 986 1178 } 987 1179 #endif
Added src/test_multiplex.h.
1 +/* 2 +** 2011 March 18 3 +** 4 +** The author disclaims copyright to this source code. In place of 5 +** a legal notice, here is a blessing: 6 +** 7 +** May you do good and not evil. 8 +** May you find forgiveness for yourself and forgive others. 9 +** May you share freely, never taking more than you give. 10 +** 11 +************************************************************************* 12 +** 13 +** This file contains a VFS "shim" - a layer that sits in between the 14 +** pager and the real VFS. 15 +** 16 +** This particular shim enforces a multiplex system on DB files. 17 +** This shim shards/partitions a single DB file into smaller 18 +** "chunks" such that the total DB file size may exceed the maximum 19 +** file size of the underlying file system. 20 +** 21 +*/ 22 + 23 +#ifndef _TEST_MULTIPLEX_H 24 +#define _TEST_MULTIPLEX_H 25 + 26 +/* 27 +** CAPI: File-control Operations Supported by Multiplex VFS 28 +** 29 +** Values interpreted by the xFileControl method of a Multiplex VFS db file-handle. 30 +** 31 +** MULTIPLEX_CTRL_ENABLE: 32 +** This file control is used to enable or disable the multiplex 33 +** shim. 34 +** 35 +** MULTIPLEX_CTRL_SET_CHUNK_SIZE: 36 +** This file control is used to set the maximum allowed chunk 37 +** size for a multiplex file set. The chunk size should be 38 +** a multiple of SQLITE_MAX_PAGE_SIZE, and will be rounded up 39 +** if not. 40 +** 41 +** MULTIPLEX_CTRL_SET_MAX_CHUNKS: 42 +** This file control is used to set the maximum number of chunks 43 +** allowed to be used for a mutliplex file set. 44 +*/ 45 +#define MULTIPLEX_CTRL_ENABLE 214014 46 +#define MULTIPLEX_CTRL_SET_CHUNK_SIZE 214015 47 +#define MULTIPLEX_CTRL_SET_MAX_CHUNKS 214016 48 + 49 +/* 50 +** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize() 51 +** 52 +** Use the VFS named zOrigVfsName as the VFS that does the actual work. 53 +** Use the default if zOrigVfsName==NULL. 54 +** 55 +** The multiplex VFS shim is named "multiplex". It will become the default 56 +** VFS if makeDefault is non-zero. 57 +** 58 +** An auto-extension is registered which will make the function 59 +** multiplex_control() available to database connections. This 60 +** function gives access to the xFileControl interface of the 61 +** multiplex VFS shim. 62 +** 63 +** SELECT multiplex_control(<op>,<val>); 64 +** 65 +** <op>=1 MULTIPLEX_CTRL_ENABLE 66 +** <val>=0 disable 67 +** <val>=1 enable 68 +** 69 +** <op>=2 MULTIPLEX_CTRL_SET_CHUNK_SIZE 70 +** <val> int, chunk size 71 +** 72 +** <op>=3 MULTIPLEX_CTRL_SET_MAX_CHUNKS 73 +** <val> int, max chunks 74 +** 75 +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once 76 +** during start-up. 77 +*/ 78 +extern int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault); 79 + 80 +/* 81 +** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown() 82 +** 83 +** All SQLite database connections must be closed before calling this 84 +** routine. 85 +** 86 +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while 87 +** shutting down in order to free all remaining multiplex groups. 88 +*/ 89 +extern int sqlite3_multiplex_shutdown(void); 90 + 91 +#endif
Changes to test/multiplex.test.
10 10 #*********************************************************************** 11 11 # 12 12 13 13 set testdir [file dirname $argv0] 14 14 source $testdir/tester.tcl 15 15 source $testdir/malloc_common.tcl 16 16 17 -set g_chunk_size 2147483648 17 +set g_chunk_size [ expr ($::SQLITE_MAX_PAGE_SIZE*16384) ] 18 18 set g_max_chunks 32 19 19 20 20 # This handles appending the chunk number 21 21 # to the end of the filename. if 22 22 # SQLITE_MULTIPLEX_EXT_OVWR is defined, then 23 23 # it overwrites the last 2 bytes of the 24 24 # file name with the chunk number. ................................................................................ 28 28 ifcapable {multiplex_ext_overwrite} { 29 29 set name [string range $name 0 [expr [string length $name]-2-1]] 30 30 } 31 31 return $name$num 32 32 } 33 33 34 34 # This saves off the parameters and calls the 35 -# underlying sqlite3_multiplex_set() API. 36 -proc multiplex_set {chunk_size max_chunks} { 35 +# underlying sqlite3_multiplex_control() API. 36 +proc multiplex_set {db name chunk_size max_chunks} { 37 37 global g_chunk_size 38 38 global g_max_chunks 39 - set g_chunk_size $chunk_size 39 + set g_chunk_size [ expr (($chunk_size+($::SQLITE_MAX_PAGE_SIZE-1)) & ~($::SQLITE_MAX_PAGE_SIZE-1)) ] 40 40 set g_max_chunks $max_chunks 41 - sqlite3_multiplex_set $chunk_size $max_chunks 41 + set rc [catch {sqlite3_multiplex_control $db $name chunk_size $chunk_size} msg] 42 + if { $rc==0 } { 43 + set rc [catch {sqlite3_multiplex_control $db $name max_chunks $max_chunks} msg] 44 + } 45 + list $msg 42 46 } 43 47 44 48 # This attempts to delete the base file and 45 49 # and files with the chunk extension. 46 50 proc multiplex_delete {name} { 47 51 global g_max_chunks 48 52 for {set i 0} {$i<$g_max_chunks} {incr i} { ................................................................................ 66 70 do_test multiplex-1.4 { sqlite3_multiplex_shutdown } {SQLITE_OK} 67 71 68 72 do_test multiplex-1.5 { sqlite3_multiplex_initialize "" 0 } {SQLITE_OK} 69 73 do_test multiplex-1.6 { sqlite3_multiplex_shutdown } {SQLITE_OK} 70 74 do_test multiplex-1.7 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} 71 75 do_test multiplex-1.8 { sqlite3_multiplex_shutdown } {SQLITE_OK} 72 76 73 -do_test multiplex-1.9 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} 74 -do_test multiplex-1.10.1 { multiplex_set 32768 16 } {SQLITE_OK} 75 -do_test multiplex-1.10.2 { multiplex_set 32768 -1 } {SQLITE_MISUSE} 76 -do_test multiplex-1.10.3 { multiplex_set -1 16 } {SQLITE_MISUSE} 77 -do_test multiplex-1.10.4 { multiplex_set 31 16 } {SQLITE_MISUSE} 78 -do_test multiplex-1.10.5 { multiplex_set 32768 100 } {SQLITE_MISUSE} 79 -do_test multiplex-1.11 { sqlite3_multiplex_shutdown } {SQLITE_OK} 80 77 78 +do_test multiplex-1.9.1 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} 79 +do_test multiplex-1.9.2 { sqlite3 db test.db } {} 80 +do_test multiplex-1.9.3 { multiplex_set db main 32768 16 } {SQLITE_OK} 81 +do_test multiplex-1.9.4 { multiplex_set db main 32768 -1 } {SQLITE_MISUSE} 82 +do_test multiplex-1.9.5 { multiplex_set db main -1 16 } {SQLITE_MISUSE} 83 +do_test multiplex-1.9.6 { multiplex_set db main 31 16 } {SQLITE_OK} 84 +do_test multiplex-1.9.7 { multiplex_set db main 32768 100 } {SQLITE_MISUSE} 85 +do_test multiplex-1.9.8 { multiplex_set db main 1073741824 1 } {SQLITE_OK} 86 +do_test multiplex-1.9.9 { db close } {} 87 +do_test multiplex-1.9.10 { sqlite3_multiplex_shutdown } {SQLITE_OK} 88 + 89 +do_test multiplex-1.10.1 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} 90 +do_test multiplex-1.10.2 { sqlite3 db test.db } {} 91 +do_test multiplex-1.10.3 { lindex [ catchsql { SELECT multiplex_control(2, 32768); } ] 0 } {0} 92 +do_test multiplex-1.10.4 { lindex [ catchsql { SELECT multiplex_control(3, -1); } ] 0 } {1} 93 +do_test multiplex-1.10.5 { lindex [ catchsql { SELECT multiplex_control(2, -1); } ] 0 } {1} 94 +do_test multiplex-1.10.6 { lindex [ catchsql { SELECT multiplex_control(2, 31); } ] 0 } {0} 95 +do_test multiplex-1.10.7 { lindex [ catchsql { SELECT multiplex_control(3, 100); } ] 0 } {1} 96 +do_test multiplex-1.10.8 { lindex [ catchsql { SELECT multiplex_control(2, 1073741824); } ] 0 } {0} 97 +do_test multiplex-1.10.9 { db close } {} 98 +do_test multiplex-1.10.10 { sqlite3_multiplex_shutdown } {SQLITE_OK} 99 + 100 +do_test multiplex-1.11.1 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} 101 +do_test multiplex-1.11.2 { sqlite3 db test.db } {} 102 +do_test multiplex-1.11.3 { sqlite3_multiplex_control db main enable 0 } {SQLITE_OK} 103 +do_test multiplex-1.11.4 { sqlite3_multiplex_control db main enable 1 } {SQLITE_OK} 104 +do_test multiplex-1.11.5 { sqlite3_multiplex_control db main enable -1 } {SQLITE_OK} 105 +do_test multiplex-1.11.6 { db close } {} 106 +do_test multiplex-1.11.7 { sqlite3_multiplex_shutdown } {SQLITE_OK} 107 + 108 +do_test multiplex-1.12.1 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} 109 +do_test multiplex-1.12.2 { sqlite3 db test.db } {} 110 +do_test multiplex-1.12.3 { lindex [ catchsql { SELECT multiplex_control(1, 0); } ] 0 } {0} 111 +do_test multiplex-1.12.4 { lindex [ catchsql { SELECT multiplex_control(1, 1); } ] 0 } {0} 112 +do_test multiplex-1.12.5 { lindex [ catchsql { SELECT multiplex_control(1, -1); } ] 0 } {0} 113 +do_test multiplex-1.12.6 { db close } {} 114 +do_test multiplex-1.12.7 { sqlite3_multiplex_shutdown } {SQLITE_OK} 115 + 116 +do_test multiplex-1.13.1 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} 117 +do_test multiplex-1.13.2 { sqlite3 db test.db } {} 118 +do_test multiplex-1.13.3 { lindex [ catchsql { SELECT multiplex_control(-1, 0); } ] 0 } {1} 119 +do_test multiplex-1.13.4 { lindex [ catchsql { SELECT multiplex_control(4, 1); } ] 0 } {1} 120 +do_test multiplex-1.13.6 { db close } {} 121 +do_test multiplex-1.13.7 { sqlite3_multiplex_shutdown } {SQLITE_OK} 81 122 82 123 #------------------------------------------------------------------------- 83 124 # Some simple warm-body tests with a single database file in rollback 84 125 # mode: 85 126 # 86 127 # multiplex-2.1.*: Test simple writing to a multiplex file. 87 128 # ................................................................................ 94 135 # afterwards. Then close the database and successfully shut 95 136 # down the multiplex system. 96 137 # 97 138 # multiplex-2.5.*: More reading/writing. 98 139 # 99 140 # multiplex-2.6.*: More reading/writing with varying small chunk sizes, as 100 141 # well as varying journal mode. 142 +# 143 +# multiplex-2.7.*: Disable/enable tests. 144 +# 101 145 102 146 sqlite3_multiplex_initialize "" 1 103 -multiplex_set 32768 16 147 +multiplex_set db main 32768 16 104 148 105 149 do_test multiplex-2.1.2 { 106 150 sqlite3 db test.db 107 151 execsql { 108 152 PRAGMA page_size=1024; 109 153 PRAGMA auto_vacuum=OFF; 110 154 PRAGMA journal_mode=DELETE; ................................................................................ 125 169 } {} 126 170 do_test multiplex-2.2.3 { file size [multiplex_name test.db 0] } {6144} 127 171 128 172 do_test multiplex-2.3.1 { 129 173 sqlite3 db2 test2.db 130 174 db2 close 131 175 } {} 176 + 132 177 133 178 do_test multiplex-2.4.1 { 134 179 sqlite3_multiplex_shutdown 135 180 } {SQLITE_MISUSE} 136 181 do_test multiplex-2.4.2 { 137 182 execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) } 138 183 } {} ................................................................................ 142 187 sqlite3_multiplex_shutdown 143 188 } {SQLITE_OK} 144 189 145 190 146 191 do_test multiplex-2.5.1 { 147 192 multiplex_delete test.db 148 193 sqlite3_multiplex_initialize "" 1 149 - multiplex_set 4096 16 194 + sqlite3 db test.db 195 + multiplex_set db main 4096 16 150 196 } {SQLITE_OK} 151 197 152 198 do_test multiplex-2.5.2 { 153 - sqlite3 db test.db 154 199 execsql { 155 200 PRAGMA page_size = 1024; 156 201 PRAGMA journal_mode = delete; 157 202 PRAGMA auto_vacuum = off; 158 203 CREATE TABLE t1(a PRIMARY KEY, b); 159 204 } 160 205 } {delete} ................................................................................ 161 206 162 207 do_test multiplex-2.5.3 { 163 208 execsql { 164 209 INSERT INTO t1 VALUES(1, 'one'); 165 210 INSERT INTO t1 VALUES(2, randomblob(4000)); 166 211 INSERT INTO t1 VALUES(3, 'three'); 167 212 INSERT INTO t1 VALUES(4, randomblob(4000)); 168 - INSERT INTO t1 VALUES(5, 'five') 213 + INSERT INTO t1 VALUES(5, 'five'); 214 + INSERT INTO t1 VALUES(6, randomblob($g_chunk_size)); 215 + INSERT INTO t1 VALUES(7, randomblob($g_chunk_size)); 169 216 } 170 217 } {} 171 218 172 219 do_test multiplex-2.5.4 { 173 220 db eval {SELECT * FROM t1 WHERE a=1} 174 221 } {1 one} 175 222 ................................................................................ 201 248 set all_journal_modes {delete persist truncate memory off} 202 249 foreach jmode $all_journal_modes { 203 250 for {set sz 151} {$sz<8000} {set sz [expr $sz+419]} { 204 251 205 252 do_test multiplex-2.6.1.$sz.$jmode { 206 253 multiplex_delete test.db 207 254 sqlite3_multiplex_initialize "" 1 208 - multiplex_set $sz 32 255 + sqlite3 db test.db 256 + multiplex_set db main $sz 32 209 257 } {SQLITE_OK} 210 258 211 259 do_test multiplex-2.6.2.$sz.$jmode { 212 - sqlite3 db test.db 213 260 db eval { 214 261 PRAGMA page_size = 1024; 215 262 PRAGMA auto_vacuum = off; 216 263 } 217 264 db eval "PRAGMA journal_mode = $jmode;" 218 265 } $jmode 219 266 ................................................................................ 239 286 db close 240 287 sqlite3_multiplex_shutdown 241 288 } {SQLITE_OK} 242 289 243 290 } 244 291 } 245 292 293 +do_test multiplex-2.7.1 { multiplex_delete test.db } {} 294 +do_test multiplex-2.7.2 { sqlite3_multiplex_initialize "" 1 } {SQLITE_OK} 295 +do_test multiplex-2.7.3 { sqlite3 db test.db } {} 296 +do_test multiplex-2.7.4 { lindex [ catchsql { SELECT multiplex_control(2, 65536); } ] 0 } {0} 297 +do_test multiplex-2.7.5 { lindex [ catchsql { SELECT multiplex_control(1, 0); } ] 0 } {0} 298 +do_test multiplex-2.7.6 { 299 + execsql { 300 + CREATE TABLE t1(a PRIMARY KEY, b); 301 + INSERT INTO t1 VALUES(1, randomblob(1000)); 302 + } 303 +} {} 304 +# verify only one file, and file size is less than chunks size 305 +do_test multiplex-2.7.7 { expr ([file size [multiplex_name test.db 0]] < 65536) } {1} 306 +do_test multiplex-2.7.8 { file exists [multiplex_name test.db 1] } {0} 307 +do_test multiplex-2.7.9 { 308 + execsql { 309 + INSERT INTO t1 VALUES(2, randomblob(65536)); 310 + } 311 +} {} 312 +# verify only one file, and file size exceeds chunks size 313 +do_test multiplex-2.7.10 { expr ([file size [multiplex_name test.db 0]] > 65536) } {1} 314 +do_test multiplex-2.7.11 { file exists [multiplex_name test.db 1] } {0} 315 +do_test multiplex-2.7.12 { db close } {} 316 +do_test multiplex-2.7.13 { sqlite3_multiplex_shutdown } {SQLITE_OK} 317 + 246 318 #------------------------------------------------------------------------- 247 319 # Try some tests with more than one connection to a database file. Still 248 320 # in rollback mode. 249 321 # 250 322 # multiplex-3.1.*: Two connections to a single database file. 251 323 # 252 324 # multiplex-3.2.*: Two connections to each of several database files (that 253 325 # are in the same multiplex group). 254 326 # 255 327 do_test multiplex-3.1.1 { 256 328 multiplex_delete test.db 257 329 sqlite3_multiplex_initialize "" 1 258 - multiplex_set 32768 16 330 + sqlite3 db test.db 331 + multiplex_set db main 32768 16 259 332 } {SQLITE_OK} 260 333 do_test multiplex-3.1.2 { 261 - sqlite3 db test.db 262 334 execsql { 263 335 PRAGMA page_size = 1024; 264 336 PRAGMA journal_mode = delete; 265 337 PRAGMA auto_vacuum = off; 266 338 CREATE TABLE t1(a PRIMARY KEY, b); 267 339 INSERT INTO t1 VALUES(1, 'one'); 268 340 } ................................................................................ 337 409 foreach db {db1a db2a db2b db1b} { catch { $db close } } 338 410 } {} 339 411 340 412 #------------------------------------------------------------------------- 341 413 # 342 414 343 415 sqlite3_multiplex_initialize "" 1 344 -multiplex_set 32768 16 416 +multiplex_set db main 32768 16 345 417 346 418 # Return a list of all currently defined multiplexs. 347 419 proc multiplex_list {} { 348 420 set allq {} 349 421 foreach q [sqlite3_multiplex_dump] { 350 422 lappend allq [lindex $q 0] 351 423 } ................................................................................ 399 471 400 472 #------------------------------------------------------------------------- 401 473 # The following tests test that the multiplex VFS handles malloc and IO 402 474 # errors. 403 475 # 404 476 405 477 sqlite3_multiplex_initialize "" 1 406 -multiplex_set 32768 16 478 +multiplex_set db main 32768 16 407 479 408 480 do_faultsim_test multiplex-5.1 -prep { 409 481 catch {db close} 410 482 } -body { 411 483 sqlite3 db test2.db 412 484 } 413 485 do_faultsim_test multiplex-5.2 -prep { ................................................................................ 444 516 } {1 {unable to open database file}} 445 517 catch { file delete test.db } 446 518 447 519 do_faultsim_test multiplex-5.5 -prep { 448 520 catch { sqlite3_multiplex_shutdown } 449 521 } -body { 450 522 sqlite3_multiplex_initialize "" 1 451 - multiplex_set 32768 16 523 + multiplex_set db main 32768 16 452 524 } 453 525 454 526 # test that mismatch filesize is detected 455 527 # 456 528 # Do not run this test if $::G(perm:presql) is set. If it is set, then the 457 529 # expected IO error will occur within the Tcl [sqlite3] wrapper, not within 458 530 # the first SQL statement executed below. This breaks the test case. ................................................................................ 469 541 PRAGMA auto_vacuum = off; 470 542 } 471 543 db eval "PRAGMA journal_mode = $jmode;" 472 544 } $jmode 473 545 do_test multiplex-5.6.2.$jmode { 474 546 execsql { 475 547 CREATE TABLE t1(a, b); 476 - INSERT INTO t1 VALUES(1, randomblob(1100)); 477 - INSERT INTO t1 VALUES(2, randomblob(1100)); 478 - INSERT INTO t1 VALUES(3, randomblob(1100)); 479 - INSERT INTO t1 VALUES(4, randomblob(1100)); 480 - INSERT INTO t1 VALUES(5, randomblob(1100)); 548 + INSERT INTO t1 VALUES(1, randomblob(15000)); 549 + INSERT INTO t1 VALUES(2, randomblob(15000)); 550 + INSERT INTO t1 VALUES(3, randomblob(15000)); 551 + INSERT INTO t1 VALUES(4, randomblob(15000)); 552 + INSERT INTO t1 VALUES(5, randomblob(15000)); 481 553 } 482 554 db close 483 555 sqlite3_multiplex_initialize "" 1 484 - multiplex_set 4096 16 485 556 sqlite3 db test.db 486 - } {} 557 + multiplex_set db main 4096 16 558 + } {SQLITE_OK} 487 559 do_test multiplex-5.6.3.$jmode { 488 560 catchsql { 489 - INSERT INTO t1 VALUES(6, randomblob(1100)); 561 + INSERT INTO t1 VALUES(6, randomblob(15000)); 490 562 } 491 563 } {1 {disk I/O error}} 492 564 do_test multiplex-5.6.4.$jmode { 493 565 db close 494 566 } {} 495 567 } 496 568 } 497 569 498 570 catch { sqlite3_multiplex_shutdown } 499 571 finish_test