4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
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.
11 *************************************************************************
13 ** This file contains a VFS "shim" - a layer that sits in between the
14 ** pager and the real VFS - that breaks up a very large database file
15 ** into two or more smaller files on disk. This is useful, for example,
16 ** in order to support large, multi-gigabyte databases on older filesystems
17 ** that limit the maximum file size to 2 GiB.
21 ** Compile this source file and link it with your application. Then
22 ** at start-time, invoke the following procedure:
24 ** int sqlite3_multiplex_initialize(
25 ** const char *zOrigVfsName, // The underlying real VFS
26 ** int makeDefault // True to make multiplex the default VFS
29 ** The procedure call above will create and register a new VFS shim named
30 ** "multiplex". The multiplex VFS will use the VFS named by zOrigVfsName to
31 ** do the actual disk I/O. (The zOrigVfsName parameter may be NULL, in
32 ** which case the default VFS at the moment sqlite3_multiplex_initialize()
33 ** is called will be used as the underlying real VFS.)
35 ** If the makeDefault parameter is TRUE then multiplex becomes the new
36 ** default VFS. Otherwise, you can use the multiplex VFS by specifying
37 ** "multiplex" as the 4th parameter to sqlite3_open_v2() or by employing
38 ** URI filenames and adding "vfs=multiplex" as a parameter to the filename
41 ** The multiplex VFS allows databases up to 32 GiB in size. But it splits
42 ** the files up into smaller pieces, so that they will work even on
43 ** filesystems that do not support large files. The default chunk size
44 ** is 2147418112 bytes (which is 64KiB less than 2GiB) but this can be
45 ** changed at compile-time by defining the SQLITE_MULTIPLEX_CHUNK_SIZE
46 ** macro. Use the "chunksize=NNNN" query parameter with a URI filename
47 ** in order to select an alternative chunk size for individual connections
54 #include "test_multiplex.h"
57 #define SQLITE_CORE 1 /* Disable the API redefinition in sqlite3ext.h */
59 #include "sqlite3ext.h"
62 ** These should be defined to be the same as the values in
63 ** sqliteInt.h. They are defined separately here so that
64 ** the multiplex VFS shim can be built as a loadable
67 #define UNUSED_PARAMETER(x) (void)(x)
68 #define MAX_PAGE_SIZE 0x10000
69 #define DEFAULT_SECTOR_SIZE 0x1000
71 /* Maximum chunk number */
72 #define MX_CHUNK_NUMBER 299
74 /* First chunk for rollback journal files */
75 #define SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET 400
76 #define SQLITE_MULTIPLEX_WAL_8_3_OFFSET 700
79 /************************ Shim Definitions ******************************/
81 #ifndef SQLITE_MULTIPLEX_VFS_NAME
82 # define SQLITE_MULTIPLEX_VFS_NAME "multiplex"
85 /* This is the limit on the chunk size. It may be changed by calling
86 ** the xFileControl() interface. It will be rounded up to a
87 ** multiple of MAX_PAGE_SIZE. We default it here to 2GiB less 64KiB.
89 #ifndef SQLITE_MULTIPLEX_CHUNK_SIZE
90 # define SQLITE_MULTIPLEX_CHUNK_SIZE 2147418112
93 /* This used to be the default limit on number of chunks, but
94 ** it is no longer enforced. There is currently no limit to the
97 ** May be changed by calling the xFileControl() interface.
99 #ifndef SQLITE_MULTIPLEX_MAX_CHUNKS
100 # define SQLITE_MULTIPLEX_MAX_CHUNKS 12
103 /************************ Object Definitions ******************************/
105 /* Forward declaration of all object types */
106 typedef struct multiplexGroup multiplexGroup
;
107 typedef struct multiplexConn multiplexConn
;
110 ** A "multiplex group" is a collection of files that collectively
111 ** makeup a single SQLite DB file. This allows the size of the DB
112 ** to exceed the limits imposed by the file system.
114 ** There is an instance of the following object for each defined multiplex
117 struct multiplexGroup
{
118 struct multiplexReal
{ /* For each chunk */
119 sqlite3_file
*p
; /* Handle for the chunk */
120 char *z
; /* Name of this chunk */
121 } *aReal
; /* list of all chunks */
122 int nReal
; /* Number of chunks */
123 char *zName
; /* Base filename of this group */
124 int nName
; /* Length of base filename */
125 int flags
; /* Flags used for original opening */
126 unsigned int szChunk
; /* Chunk size used for this group */
127 unsigned char bEnabled
; /* TRUE to use Multiplex VFS for this file */
128 unsigned char bTruncate
; /* TRUE to enable truncation of databases */
132 ** An instance of the following object represents each open connection
133 ** to a file that is multiplex'ed. This object is a
134 ** subclass of sqlite3_file. The sqlite3_file object for the underlying
135 ** VFS is appended to this structure.
137 struct multiplexConn
{
138 sqlite3_file base
; /* Base class - must be first */
139 multiplexGroup
*pGroup
; /* The underlying group of files */
142 /************************* Global Variables **********************************/
144 ** All global variables used by this file are containing within the following
145 ** gMultiplex structure.
148 /* The pOrigVfs is the real, original underlying VFS implementation.
149 ** Most operations pass-through to the real VFS. This value is read-only
150 ** during operation. It is only modified at start-time and thus does not
153 sqlite3_vfs
*pOrigVfs
;
155 /* The sThisVfs is the VFS structure used by this shim. It is initialized
156 ** at start-time and thus does not require a mutex
158 sqlite3_vfs sThisVfs
;
160 /* The sIoMethods defines the methods used by sqlite3_file objects
161 ** associated with this shim. It is initialized at start-time and does
162 ** not require a mutex.
164 ** When the underlying VFS is called to open a file, it might return
165 ** either a version 1 or a version 2 sqlite3_file object. This shim
166 ** has to create a wrapper sqlite3_file of the same version. Hence
167 ** there are two I/O method structures, one for version 1 and the other
170 sqlite3_io_methods sIoMethodsV1
;
171 sqlite3_io_methods sIoMethodsV2
;
173 /* True when this shim has been initialized.
178 /************************* Utility Routines *********************************/
180 ** Compute a string length that is limited to what can be stored in
181 ** lower 30 bits of a 32-bit signed integer.
183 ** The value returned will never be negative. Nor will it ever be greater
184 ** than the actual length of the string. For very long strings (greater
185 ** than 1GiB) the value returned might be less than the true string length.
187 static int multiplexStrlen30(const char *z
){
190 while( *z2
){ z2
++; }
191 return 0x3fffffff & (int)(z2
- z
);
195 ** Generate the file-name for chunk iChunk of the group with base name
196 ** zBase. The file-name is written to buffer zOut before returning. Buffer
197 ** zOut must be allocated by the caller so that it is at least (nBase+5)
198 ** bytes in size, where nBase is the length of zBase, not including the
201 ** If iChunk is 0 (or 400 - the number for the first journal file chunk),
202 ** the output is a copy of the input string. Otherwise, if
203 ** SQLITE_ENABLE_8_3_NAMES is not defined or the input buffer does not contain
204 ** a "." character, then the output is a copy of the input string with the
205 ** three-digit zero-padded decimal representation if iChunk appended to it.
208 ** zBase="test.db", iChunk=4 -> zOut="test.db004"
210 ** Or, if SQLITE_ENABLE_8_3_NAMES is defined and the input buffer contains
211 ** a "." character, then everything after the "." is replaced by the
212 ** three-digit representation of iChunk.
214 ** zBase="test.db", iChunk=4 -> zOut="test.004"
216 ** The output buffer string is terminated by 2 0x00 bytes. This makes it safe
217 ** to pass to sqlite3_uri_parameter() and similar.
219 static void multiplexFilename(
220 const char *zBase
, /* Filename for chunk 0 */
221 int nBase
, /* Size of zBase in bytes (without \0) */
222 int flags
, /* Flags used to open file */
223 int iChunk
, /* Chunk to generate filename for */
224 char *zOut
/* Buffer to write generated name to */
227 memcpy(zOut
, zBase
, n
+1);
228 if( iChunk
!=0 && iChunk
<=MX_CHUNK_NUMBER
){
229 #ifdef SQLITE_ENABLE_8_3_NAMES
231 for(i
=n
-1; i
>0 && i
>=n
-4 && zOut
[i
]!='.'; i
--){}
232 if( i
>=n
-4 ) n
= i
+1;
233 if( flags
& SQLITE_OPEN_MAIN_JOURNAL
){
234 /* The extensions on overflow files for main databases are 001, 002,
235 ** 003 and so forth. To avoid name collisions, add 400 to the
236 ** extensions of journal files so that they are 401, 402, 403, ....
238 iChunk
+= SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET
;
239 }else if( flags
& SQLITE_OPEN_WAL
){
240 /* To avoid name collisions, add 700 to the
241 ** extensions of WAL files so that they are 701, 702, 703, ....
243 iChunk
+= SQLITE_MULTIPLEX_WAL_8_3_OFFSET
;
246 sqlite3_snprintf(4,&zOut
[n
],"%03d",iChunk
);
250 assert( zOut
[n
]=='\0' );
254 /* Compute the filename for the iChunk-th chunk
256 static int multiplexSubFilename(multiplexGroup
*pGroup
, int iChunk
){
257 if( iChunk
>=pGroup
->nReal
){
258 struct multiplexReal
*p
;
259 p
= sqlite3_realloc64(pGroup
->aReal
, (iChunk
+1)*sizeof(*p
));
263 memset(&p
[pGroup
->nReal
], 0, sizeof(p
[0])*(iChunk
+1-pGroup
->nReal
));
265 pGroup
->nReal
= iChunk
+1;
267 if( pGroup
->zName
&& pGroup
->aReal
[iChunk
].z
==0 ){
269 int n
= pGroup
->nName
;
270 pGroup
->aReal
[iChunk
].z
= z
= sqlite3_malloc64( n
+5 );
274 multiplexFilename(pGroup
->zName
, pGroup
->nName
, pGroup
->flags
, iChunk
, z
);
279 /* Translate an sqlite3_file* that is really a multiplexGroup* into
280 ** the sqlite3_file* for the underlying original VFS.
282 ** For chunk 0, the pGroup->flags determines whether or not a new file
283 ** is created if it does not already exist. For chunks 1 and higher, the
284 ** file is created only if createFlag is 1.
286 static sqlite3_file
*multiplexSubOpen(
287 multiplexGroup
*pGroup
, /* The multiplexor group */
288 int iChunk
, /* Which chunk to open. 0==original file */
289 int *rc
, /* Result code in and out */
290 int *pOutFlags
, /* Output flags */
291 int createFlag
/* True to create if iChunk>0 */
293 sqlite3_file
*pSubOpen
= 0;
294 sqlite3_vfs
*pOrigVfs
= gMultiplex
.pOrigVfs
; /* Real VFS */
296 #ifdef SQLITE_ENABLE_8_3_NAMES
297 /* If JOURNAL_8_3_OFFSET is set to (say) 400, then any overflow files are
298 ** part of a database journal are named db.401, db.402, and so on. A
299 ** database may therefore not grow to larger than 400 chunks. Attempting
300 ** to open chunk 401 indicates the database is full. */
301 if( iChunk
>=SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET
){
302 sqlite3_log(SQLITE_FULL
, "multiplexed chunk overflow: %s", pGroup
->zName
);
308 *rc
= multiplexSubFilename(pGroup
, iChunk
);
309 if( (*rc
)==SQLITE_OK
&& (pSubOpen
= pGroup
->aReal
[iChunk
].p
)==0 ){
311 flags
= pGroup
->flags
;
313 flags
|= SQLITE_OPEN_CREATE
;
314 }else if( iChunk
==0 ){
316 }else if( pGroup
->aReal
[iChunk
].z
==0 ){
319 *rc
= pOrigVfs
->xAccess(pOrigVfs
, pGroup
->aReal
[iChunk
].z
,
320 SQLITE_ACCESS_EXISTS
, &bExists
);
321 if( *rc
|| !bExists
){
323 sqlite3_log(*rc
, "multiplexor.xAccess failure on %s",
324 pGroup
->aReal
[iChunk
].z
);
328 flags
&= ~SQLITE_OPEN_CREATE
;
330 pSubOpen
= sqlite3_malloc64( pOrigVfs
->szOsFile
);
332 *rc
= SQLITE_IOERR_NOMEM
;
335 pGroup
->aReal
[iChunk
].p
= pSubOpen
;
336 *rc
= pOrigVfs
->xOpen(pOrigVfs
, pGroup
->aReal
[iChunk
].z
, pSubOpen
,
338 if( (*rc
)!=SQLITE_OK
){
339 sqlite3_log(*rc
, "multiplexor.xOpen failure on %s",
340 pGroup
->aReal
[iChunk
].z
);
341 sqlite3_free(pSubOpen
);
342 pGroup
->aReal
[iChunk
].p
= 0;
350 ** Return the size, in bytes, of chunk number iChunk. If that chunk
351 ** does not exist, then return 0. This function does not distingish between
352 ** non-existant files and zero-length files.
354 static sqlite3_int64
multiplexSubSize(
355 multiplexGroup
*pGroup
, /* The multiplexor group */
356 int iChunk
, /* Which chunk to open. 0==original file */
357 int *rc
/* Result code in and out */
360 sqlite3_int64 sz
= 0;
363 pSub
= multiplexSubOpen(pGroup
, iChunk
, rc
, NULL
, 0);
364 if( pSub
==0 ) return 0;
365 *rc
= pSub
->pMethods
->xFileSize(pSub
, &sz
);
370 ** This is the implementation of the multiplex_control() SQL function.
372 static void multiplexControlFunc(
373 sqlite3_context
*context
,
378 sqlite3
*db
= sqlite3_context_db_handle(context
);
382 if( !db
|| argc
!=2 ){
386 op
= sqlite3_value_int(argv
[0]);
387 iVal
= sqlite3_value_int(argv
[1]);
388 /* map function op to file_control op */
391 op
= MULTIPLEX_CTRL_ENABLE
;
394 op
= MULTIPLEX_CTRL_SET_CHUNK_SIZE
;
397 op
= MULTIPLEX_CTRL_SET_MAX_CHUNKS
;
400 rc
= SQLITE_NOTFOUND
;
405 rc
= sqlite3_file_control(db
, 0, op
, &iVal
);
407 sqlite3_result_error_code(context
, rc
);
411 ** This is the entry point to register the auto-extension for the
412 ** multiplex_control() function.
414 static int multiplexFuncInit(
417 const sqlite3_api_routines
*pApi
420 rc
= sqlite3_create_function(db
, "multiplex_control", 2, SQLITE_ANY
,
421 0, multiplexControlFunc
, 0, 0);
426 ** Close a single sub-file in the connection group.
428 static void multiplexSubClose(
429 multiplexGroup
*pGroup
,
431 sqlite3_vfs
*pOrigVfs
433 sqlite3_file
*pSubOpen
= pGroup
->aReal
[iChunk
].p
;
435 pSubOpen
->pMethods
->xClose(pSubOpen
);
436 if( pOrigVfs
&& pGroup
->aReal
[iChunk
].z
){
437 pOrigVfs
->xDelete(pOrigVfs
, pGroup
->aReal
[iChunk
].z
, 0);
439 sqlite3_free(pGroup
->aReal
[iChunk
].p
);
441 sqlite3_free(pGroup
->aReal
[iChunk
].z
);
442 memset(&pGroup
->aReal
[iChunk
], 0, sizeof(pGroup
->aReal
[iChunk
]));
446 ** Deallocate memory held by a multiplexGroup
448 static void multiplexFreeComponents(multiplexGroup
*pGroup
){
450 for(i
=0; i
<pGroup
->nReal
; i
++){ multiplexSubClose(pGroup
, i
, 0); }
451 sqlite3_free(pGroup
->aReal
);
457 /************************* VFS Method Wrappers *****************************/
460 ** This is the xOpen method used for the "multiplex" VFS.
462 ** Most of the work is done by the underlying original VFS. This method
463 ** simply links the new file into the appropriate multiplex group if it is a
464 ** file that needs to be tracked.
466 static int multiplexOpen(
467 sqlite3_vfs
*pVfs
, /* The multiplex VFS */
468 const char *zName
, /* Name of file to be opened */
469 sqlite3_file
*pConn
, /* Fill in this file descriptor */
470 int flags
, /* Flags to control the opening */
471 int *pOutFlags
/* Flags showing results of opening */
473 int rc
= SQLITE_OK
; /* Result code */
474 multiplexConn
*pMultiplexOpen
; /* The new multiplex file descriptor */
475 multiplexGroup
*pGroup
= 0; /* Corresponding multiplexGroup object */
476 sqlite3_file
*pSubOpen
= 0; /* Real file descriptor */
477 sqlite3_vfs
*pOrigVfs
= gMultiplex
.pOrigVfs
; /* Real VFS */
482 UNUSED_PARAMETER(pVfs
);
483 memset(pConn
, 0, pVfs
->szOsFile
);
484 assert( zName
|| (flags
& SQLITE_OPEN_DELETEONCLOSE
) );
486 /* We need to create a group structure and manage
487 ** access to this group of files.
489 pMultiplexOpen
= (multiplexConn
*)pConn
;
492 /* allocate space for group */
493 nName
= zName
? multiplexStrlen30(zName
) : 0;
494 sz
= sizeof(multiplexGroup
) /* multiplexGroup */
495 + nName
+ 1; /* zName */
496 pGroup
= sqlite3_malloc64( sz
);
503 const char *zUri
= (flags
& SQLITE_OPEN_URI
) ? zName
: 0;
504 /* assign pointers to extra space allocated */
505 memset(pGroup
, 0, sz
);
506 pMultiplexOpen
->pGroup
= pGroup
;
507 pGroup
->bEnabled
= (unsigned char)-1;
508 pGroup
->bTruncate
= (unsigned char)sqlite3_uri_boolean(zUri
, "truncate",
509 (flags
& SQLITE_OPEN_MAIN_DB
)==0);
510 pGroup
->szChunk
= (int)sqlite3_uri_int64(zUri
, "chunksize",
511 SQLITE_MULTIPLEX_CHUNK_SIZE
);
512 pGroup
->szChunk
= (pGroup
->szChunk
+0xffff)&~0xffff;
514 char *p
= (char *)&pGroup
[1];
516 memcpy(pGroup
->zName
, zName
, nName
+1);
517 pGroup
->nName
= nName
;
519 if( pGroup
->bEnabled
){
520 /* Make sure that the chunksize is such that the pending byte does not
521 ** falls at the end of a chunk. A region of up to 64K following
522 ** the pending byte is never written, so if the pending byte occurs
523 ** near the end of a chunk, that chunk will be too small. */
524 #ifndef SQLITE_OMIT_WSD
525 extern int sqlite3PendingByte
;
527 int sqlite3PendingByte
= 0x40000000;
529 while( (sqlite3PendingByte
% pGroup
->szChunk
)>=(pGroup
->szChunk
-65536) ){
530 pGroup
->szChunk
+= 65536;
533 pGroup
->flags
= flags
;
534 rc
= multiplexSubFilename(pGroup
, 1);
536 pSubOpen
= multiplexSubOpen(pGroup
, 0, &rc
, pOutFlags
, 0);
537 if( pSubOpen
==0 && rc
==SQLITE_OK
) rc
= SQLITE_CANTOPEN
;
542 rc
= pSubOpen
->pMethods
->xFileSize(pSubOpen
, &sz64
);
543 if( rc
==SQLITE_OK
&& zName
){
545 if( flags
& SQLITE_OPEN_MASTER_JOURNAL
){
546 pGroup
->bEnabled
= 0;
549 if( flags
& SQLITE_OPEN_MAIN_JOURNAL
){
550 /* If opening a main journal file and the first chunk is zero
551 ** bytes in size, delete any subsequent chunks from the
555 rc
= pOrigVfs
->xAccess(pOrigVfs
,
556 pGroup
->aReal
[iChunk
].z
, SQLITE_ACCESS_EXISTS
, &bExists
558 if( rc
==SQLITE_OK
&& bExists
){
559 rc
= pOrigVfs
->xDelete(pOrigVfs
, pGroup
->aReal
[iChunk
].z
, 0);
561 rc
= multiplexSubFilename(pGroup
, ++iChunk
);
564 }while( rc
==SQLITE_OK
&& bExists
);
567 /* If the first overflow file exists and if the size of the main file
568 ** is different from the chunk size, that means the chunk size is set
569 ** set incorrectly. So fix it.
571 ** Or, if the first overflow file does not exist and the main file is
572 ** larger than the chunk size, that means the chunk size is too small.
573 ** But we have no way of determining the intended chunk size, so
574 ** just disable the multiplexor all togethre.
576 rc
= pOrigVfs
->xAccess(pOrigVfs
, pGroup
->aReal
[1].z
,
577 SQLITE_ACCESS_EXISTS
, &bExists
);
578 bExists
= multiplexSubSize(pGroup
, 1, &rc
)>0;
579 if( rc
==SQLITE_OK
&& bExists
&& sz64
==(sz64
&0xffff0000) && sz64
>0
580 && sz64
!=pGroup
->szChunk
){
581 pGroup
->szChunk
= (int)sz64
;
582 }else if( rc
==SQLITE_OK
&& !bExists
&& sz64
>pGroup
->szChunk
){
583 pGroup
->bEnabled
= 0;
590 if( pSubOpen
->pMethods
->iVersion
==1 ){
591 pMultiplexOpen
->base
.pMethods
= &gMultiplex
.sIoMethodsV1
;
593 pMultiplexOpen
->base
.pMethods
= &gMultiplex
.sIoMethodsV2
;
596 multiplexFreeComponents(pGroup
);
597 sqlite3_free(pGroup
);
600 sqlite3_free(zToFree
);
605 ** This is the xDelete method used for the "multiplex" VFS.
606 ** It attempts to delete the filename specified.
608 static int multiplexDelete(
609 sqlite3_vfs
*pVfs
, /* The multiplex VFS */
610 const char *zName
, /* Name of file to delete */
614 sqlite3_vfs
*pOrigVfs
= gMultiplex
.pOrigVfs
; /* Real VFS */
615 rc
= pOrigVfs
->xDelete(pOrigVfs
, zName
, syncDir
);
617 /* If the main chunk was deleted successfully, also delete any subsequent
618 ** chunks - starting with the last (highest numbered).
620 int nName
= (int)strlen(zName
);
622 z
= sqlite3_malloc64(nName
+ 5);
624 rc
= SQLITE_IOERR_NOMEM
;
629 multiplexFilename(zName
, nName
, SQLITE_OPEN_MAIN_JOURNAL
, ++iChunk
, z
);
630 rc
= pOrigVfs
->xAccess(pOrigVfs
, z
, SQLITE_ACCESS_EXISTS
, &bExists
);
631 }while( rc
==SQLITE_OK
&& bExists
);
632 while( rc
==SQLITE_OK
&& iChunk
>1 ){
633 multiplexFilename(zName
, nName
, SQLITE_OPEN_MAIN_JOURNAL
, --iChunk
, z
);
634 rc
= pOrigVfs
->xDelete(pOrigVfs
, z
, syncDir
);
639 multiplexFilename(zName
, nName
, SQLITE_OPEN_WAL
, ++iChunk
, z
);
640 rc
= pOrigVfs
->xAccess(pOrigVfs
, z
, SQLITE_ACCESS_EXISTS
, &bExists
);
641 }while( rc
==SQLITE_OK
&& bExists
);
642 while( rc
==SQLITE_OK
&& iChunk
>1 ){
643 multiplexFilename(zName
, nName
, SQLITE_OPEN_WAL
, --iChunk
, z
);
644 rc
= pOrigVfs
->xDelete(pOrigVfs
, z
, syncDir
);
653 static int multiplexAccess(sqlite3_vfs
*a
, const char *b
, int c
, int *d
){
654 return gMultiplex
.pOrigVfs
->xAccess(gMultiplex
.pOrigVfs
, b
, c
, d
);
656 static int multiplexFullPathname(sqlite3_vfs
*a
, const char *b
, int c
, char *d
){
657 return gMultiplex
.pOrigVfs
->xFullPathname(gMultiplex
.pOrigVfs
, b
, c
, d
);
659 static void *multiplexDlOpen(sqlite3_vfs
*a
, const char *b
){
660 return gMultiplex
.pOrigVfs
->xDlOpen(gMultiplex
.pOrigVfs
, b
);
662 static void multiplexDlError(sqlite3_vfs
*a
, int b
, char *c
){
663 gMultiplex
.pOrigVfs
->xDlError(gMultiplex
.pOrigVfs
, b
, c
);
665 static void (*multiplexDlSym(sqlite3_vfs
*a
, void *b
, const char *c
))(void){
666 return gMultiplex
.pOrigVfs
->xDlSym(gMultiplex
.pOrigVfs
, b
, c
);
668 static void multiplexDlClose(sqlite3_vfs
*a
, void *b
){
669 gMultiplex
.pOrigVfs
->xDlClose(gMultiplex
.pOrigVfs
, b
);
671 static int multiplexRandomness(sqlite3_vfs
*a
, int b
, char *c
){
672 return gMultiplex
.pOrigVfs
->xRandomness(gMultiplex
.pOrigVfs
, b
, c
);
674 static int multiplexSleep(sqlite3_vfs
*a
, int b
){
675 return gMultiplex
.pOrigVfs
->xSleep(gMultiplex
.pOrigVfs
, b
);
677 static int multiplexCurrentTime(sqlite3_vfs
*a
, double *b
){
678 return gMultiplex
.pOrigVfs
->xCurrentTime(gMultiplex
.pOrigVfs
, b
);
680 static int multiplexGetLastError(sqlite3_vfs
*a
, int b
, char *c
){
681 if( gMultiplex
.pOrigVfs
->xGetLastError
){
682 return gMultiplex
.pOrigVfs
->xGetLastError(gMultiplex
.pOrigVfs
, b
, c
);
687 static int multiplexCurrentTimeInt64(sqlite3_vfs
*a
, sqlite3_int64
*b
){
688 return gMultiplex
.pOrigVfs
->xCurrentTimeInt64(gMultiplex
.pOrigVfs
, b
);
691 /************************ I/O Method Wrappers *******************************/
693 /* xClose requests get passed through to the original VFS.
694 ** We loop over all open chunk handles and close them.
695 ** The group structure for this file is unlinked from
696 ** our list of groups and freed.
698 static int multiplexClose(sqlite3_file
*pConn
){
699 multiplexConn
*p
= (multiplexConn
*)pConn
;
700 multiplexGroup
*pGroup
= p
->pGroup
;
702 multiplexFreeComponents(pGroup
);
703 sqlite3_free(pGroup
);
707 /* Pass xRead requests thru to the original VFS after
708 ** determining the correct chunk to operate on.
709 ** Break up reads across chunk boundaries.
711 static int multiplexRead(
717 multiplexConn
*p
= (multiplexConn
*)pConn
;
718 multiplexGroup
*pGroup
= p
->pGroup
;
720 if( !pGroup
->bEnabled
){
721 sqlite3_file
*pSubOpen
= multiplexSubOpen(pGroup
, 0, &rc
, NULL
, 0);
723 rc
= SQLITE_IOERR_READ
;
725 rc
= pSubOpen
->pMethods
->xRead(pSubOpen
, pBuf
, iAmt
, iOfst
);
729 int i
= (int)(iOfst
/ pGroup
->szChunk
);
730 sqlite3_file
*pSubOpen
;
731 pSubOpen
= multiplexSubOpen(pGroup
, i
, &rc
, NULL
, 1);
733 int extra
= ((int)(iOfst
% pGroup
->szChunk
) + iAmt
) - pGroup
->szChunk
;
734 if( extra
<0 ) extra
= 0;
736 rc
= pSubOpen
->pMethods
->xRead(pSubOpen
, pBuf
, iAmt
,
737 iOfst
% pGroup
->szChunk
);
738 if( rc
!=SQLITE_OK
) break;
739 pBuf
= (char *)pBuf
+ iAmt
;
743 rc
= SQLITE_IOERR_READ
;
752 /* Pass xWrite requests thru to the original VFS after
753 ** determining the correct chunk to operate on.
754 ** Break up writes across chunk boundaries.
756 static int multiplexWrite(
762 multiplexConn
*p
= (multiplexConn
*)pConn
;
763 multiplexGroup
*pGroup
= p
->pGroup
;
765 if( !pGroup
->bEnabled
){
766 sqlite3_file
*pSubOpen
= multiplexSubOpen(pGroup
, 0, &rc
, NULL
, 0);
768 rc
= SQLITE_IOERR_WRITE
;
770 rc
= pSubOpen
->pMethods
->xWrite(pSubOpen
, pBuf
, iAmt
, iOfst
);
773 while( rc
==SQLITE_OK
&& iAmt
>0 ){
774 int i
= (int)(iOfst
/ pGroup
->szChunk
);
775 sqlite3_file
*pSubOpen
= multiplexSubOpen(pGroup
, i
, &rc
, NULL
, 1);
777 int extra
= ((int)(iOfst
% pGroup
->szChunk
) + iAmt
) -
779 if( extra
<0 ) extra
= 0;
781 rc
= pSubOpen
->pMethods
->xWrite(pSubOpen
, pBuf
, iAmt
,
782 iOfst
% pGroup
->szChunk
);
783 pBuf
= (char *)pBuf
+ iAmt
;
792 /* Pass xTruncate requests thru to the original VFS after
793 ** determining the correct chunk to operate on. Delete any
794 ** chunks above the truncate mark.
796 static int multiplexTruncate(sqlite3_file
*pConn
, sqlite3_int64 size
){
797 multiplexConn
*p
= (multiplexConn
*)pConn
;
798 multiplexGroup
*pGroup
= p
->pGroup
;
800 if( !pGroup
->bEnabled
){
801 sqlite3_file
*pSubOpen
= multiplexSubOpen(pGroup
, 0, &rc
, NULL
, 0);
803 rc
= SQLITE_IOERR_TRUNCATE
;
805 rc
= pSubOpen
->pMethods
->xTruncate(pSubOpen
, size
);
809 int iBaseGroup
= (int)(size
/ pGroup
->szChunk
);
810 sqlite3_file
*pSubOpen
;
811 sqlite3_vfs
*pOrigVfs
= gMultiplex
.pOrigVfs
; /* Real VFS */
812 /* delete the chunks above the truncate limit */
813 for(i
= pGroup
->nReal
-1; i
>iBaseGroup
&& rc
==SQLITE_OK
; i
--){
814 if( pGroup
->bTruncate
){
815 multiplexSubClose(pGroup
, i
, pOrigVfs
);
817 pSubOpen
= multiplexSubOpen(pGroup
, i
, &rc
, 0, 0);
819 rc
= pSubOpen
->pMethods
->xTruncate(pSubOpen
, 0);
824 pSubOpen
= multiplexSubOpen(pGroup
, iBaseGroup
, &rc
, 0, 0);
826 rc
= pSubOpen
->pMethods
->xTruncate(pSubOpen
, size
% pGroup
->szChunk
);
829 if( rc
) rc
= SQLITE_IOERR_TRUNCATE
;
834 /* Pass xSync requests through to the original VFS without change
836 static int multiplexSync(sqlite3_file
*pConn
, int flags
){
837 multiplexConn
*p
= (multiplexConn
*)pConn
;
838 multiplexGroup
*pGroup
= p
->pGroup
;
841 for(i
=0; i
<pGroup
->nReal
; i
++){
842 sqlite3_file
*pSubOpen
= pGroup
->aReal
[i
].p
;
844 int rc2
= pSubOpen
->pMethods
->xSync(pSubOpen
, flags
);
845 if( rc2
!=SQLITE_OK
) rc
= rc2
;
851 /* Pass xFileSize requests through to the original VFS.
852 ** Aggregate the size of all the chunks before returning.
854 static int multiplexFileSize(sqlite3_file
*pConn
, sqlite3_int64
*pSize
){
855 multiplexConn
*p
= (multiplexConn
*)pConn
;
856 multiplexGroup
*pGroup
= p
->pGroup
;
859 if( !pGroup
->bEnabled
){
860 sqlite3_file
*pSubOpen
= multiplexSubOpen(pGroup
, 0, &rc
, NULL
, 0);
862 rc
= SQLITE_IOERR_FSTAT
;
864 rc
= pSubOpen
->pMethods
->xFileSize(pSubOpen
, pSize
);
868 for(i
=0; rc
==SQLITE_OK
; i
++){
869 sqlite3_int64 sz
= multiplexSubSize(pGroup
, i
, &rc
);
871 *pSize
= i
*(sqlite3_int64
)pGroup
->szChunk
+ sz
;
877 /* Pass xLock requests through to the original VFS unchanged.
879 static int multiplexLock(sqlite3_file
*pConn
, int lock
){
880 multiplexConn
*p
= (multiplexConn
*)pConn
;
882 sqlite3_file
*pSubOpen
= multiplexSubOpen(p
->pGroup
, 0, &rc
, NULL
, 0);
884 return pSubOpen
->pMethods
->xLock(pSubOpen
, lock
);
889 /* Pass xUnlock requests through to the original VFS unchanged.
891 static int multiplexUnlock(sqlite3_file
*pConn
, int lock
){
892 multiplexConn
*p
= (multiplexConn
*)pConn
;
894 sqlite3_file
*pSubOpen
= multiplexSubOpen(p
->pGroup
, 0, &rc
, NULL
, 0);
896 return pSubOpen
->pMethods
->xUnlock(pSubOpen
, lock
);
898 return SQLITE_IOERR_UNLOCK
;
901 /* Pass xCheckReservedLock requests through to the original VFS unchanged.
903 static int multiplexCheckReservedLock(sqlite3_file
*pConn
, int *pResOut
){
904 multiplexConn
*p
= (multiplexConn
*)pConn
;
906 sqlite3_file
*pSubOpen
= multiplexSubOpen(p
->pGroup
, 0, &rc
, NULL
, 0);
908 return pSubOpen
->pMethods
->xCheckReservedLock(pSubOpen
, pResOut
);
910 return SQLITE_IOERR_CHECKRESERVEDLOCK
;
913 /* Pass xFileControl requests through to the original VFS unchanged,
914 ** except for any MULTIPLEX_CTRL_* requests here.
916 static int multiplexFileControl(sqlite3_file
*pConn
, int op
, void *pArg
){
917 multiplexConn
*p
= (multiplexConn
*)pConn
;
918 multiplexGroup
*pGroup
= p
->pGroup
;
919 int rc
= SQLITE_ERROR
;
920 sqlite3_file
*pSubOpen
;
922 if( !gMultiplex
.isInitialized
) return SQLITE_MISUSE
;
924 case MULTIPLEX_CTRL_ENABLE
:
926 int bEnabled
= *(int *)pArg
;
927 pGroup
->bEnabled
= (unsigned char)bEnabled
;
931 case MULTIPLEX_CTRL_SET_CHUNK_SIZE
:
933 unsigned int szChunk
= *(unsigned*)pArg
;
937 /* Round up to nearest multiple of MAX_PAGE_SIZE. */
938 szChunk
= (szChunk
+ (MAX_PAGE_SIZE
-1));
939 szChunk
&= ~(MAX_PAGE_SIZE
-1);
940 pGroup
->szChunk
= szChunk
;
945 case MULTIPLEX_CTRL_SET_MAX_CHUNKS
:
948 case SQLITE_FCNTL_SIZE_HINT
:
949 case SQLITE_FCNTL_CHUNK_SIZE
:
953 case SQLITE_FCNTL_PRAGMA
: {
954 char **aFcntl
= (char**)pArg
;
956 ** EVIDENCE-OF: R-29875-31678 The argument to the SQLITE_FCNTL_PRAGMA
957 ** file control is an array of pointers to strings (char**) in which the
958 ** second element of the array is the name of the pragma and the third
959 ** element is the argument to the pragma or NULL if the pragma has no
962 if( aFcntl
[1] && sqlite3_stricmp(aFcntl
[1],"multiplex_truncate")==0 ){
963 if( aFcntl
[2] && aFcntl
[2][0] ){
964 if( sqlite3_stricmp(aFcntl
[2], "on")==0
965 || sqlite3_stricmp(aFcntl
[2], "1")==0 ){
966 pGroup
->bTruncate
= 1;
968 if( sqlite3_stricmp(aFcntl
[2], "off")==0
969 || sqlite3_stricmp(aFcntl
[2], "0")==0 ){
970 pGroup
->bTruncate
= 0;
973 /* EVIDENCE-OF: R-27806-26076 The handler for an SQLITE_FCNTL_PRAGMA
974 ** file control can optionally make the first element of the char**
975 ** argument point to a string obtained from sqlite3_mprintf() or the
976 ** equivalent and that string will become the result of the pragma
977 ** or the error message if the pragma fails.
979 aFcntl
[0] = sqlite3_mprintf(pGroup
->bTruncate
? "on" : "off");
983 /* If the multiplexor does not handle the pragma, pass it through
984 ** into the default case. */
987 pSubOpen
= multiplexSubOpen(pGroup
, 0, &rc
, NULL
, 0);
989 rc
= pSubOpen
->pMethods
->xFileControl(pSubOpen
, op
, pArg
);
990 if( op
==SQLITE_FCNTL_VFSNAME
&& rc
==SQLITE_OK
){
991 *(char**)pArg
= sqlite3_mprintf("multiplex/%z", *(char**)pArg
);
999 /* Pass xSectorSize requests through to the original VFS unchanged.
1001 static int multiplexSectorSize(sqlite3_file
*pConn
){
1002 multiplexConn
*p
= (multiplexConn
*)pConn
;
1004 sqlite3_file
*pSubOpen
= multiplexSubOpen(p
->pGroup
, 0, &rc
, NULL
, 0);
1005 if( pSubOpen
&& pSubOpen
->pMethods
->xSectorSize
){
1006 return pSubOpen
->pMethods
->xSectorSize(pSubOpen
);
1008 return DEFAULT_SECTOR_SIZE
;
1011 /* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
1013 static int multiplexDeviceCharacteristics(sqlite3_file
*pConn
){
1014 multiplexConn
*p
= (multiplexConn
*)pConn
;
1016 sqlite3_file
*pSubOpen
= multiplexSubOpen(p
->pGroup
, 0, &rc
, NULL
, 0);
1018 return pSubOpen
->pMethods
->xDeviceCharacteristics(pSubOpen
);
1023 /* Pass xShmMap requests through to the original VFS unchanged.
1025 static int multiplexShmMap(
1026 sqlite3_file
*pConn
, /* Handle open on database file */
1027 int iRegion
, /* Region to retrieve */
1028 int szRegion
, /* Size of regions */
1029 int bExtend
, /* True to extend file if necessary */
1030 void volatile **pp
/* OUT: Mapped memory */
1032 multiplexConn
*p
= (multiplexConn
*)pConn
;
1034 sqlite3_file
*pSubOpen
= multiplexSubOpen(p
->pGroup
, 0, &rc
, NULL
, 0);
1036 return pSubOpen
->pMethods
->xShmMap(pSubOpen
, iRegion
, szRegion
, bExtend
,pp
);
1038 return SQLITE_IOERR
;
1041 /* Pass xShmLock requests through to the original VFS unchanged.
1043 static int multiplexShmLock(
1044 sqlite3_file
*pConn
, /* Database file holding the shared memory */
1045 int ofst
, /* First lock to acquire or release */
1046 int n
, /* Number of locks to acquire or release */
1047 int flags
/* What to do with the lock */
1049 multiplexConn
*p
= (multiplexConn
*)pConn
;
1051 sqlite3_file
*pSubOpen
= multiplexSubOpen(p
->pGroup
, 0, &rc
, NULL
, 0);
1053 return pSubOpen
->pMethods
->xShmLock(pSubOpen
, ofst
, n
, flags
);
1058 /* Pass xShmBarrier requests through to the original VFS unchanged.
1060 static void multiplexShmBarrier(sqlite3_file
*pConn
){
1061 multiplexConn
*p
= (multiplexConn
*)pConn
;
1063 sqlite3_file
*pSubOpen
= multiplexSubOpen(p
->pGroup
, 0, &rc
, NULL
, 0);
1065 pSubOpen
->pMethods
->xShmBarrier(pSubOpen
);
1069 /* Pass xShmUnmap requests through to the original VFS unchanged.
1071 static int multiplexShmUnmap(sqlite3_file
*pConn
, int deleteFlag
){
1072 multiplexConn
*p
= (multiplexConn
*)pConn
;
1074 sqlite3_file
*pSubOpen
= multiplexSubOpen(p
->pGroup
, 0, &rc
, NULL
, 0);
1076 return pSubOpen
->pMethods
->xShmUnmap(pSubOpen
, deleteFlag
);
1081 /************************** Public Interfaces *****************************/
1083 ** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize()
1085 ** Use the VFS named zOrigVfsName as the VFS that does the actual work.
1086 ** Use the default if zOrigVfsName==NULL.
1088 ** The multiplex VFS shim is named "multiplex". It will become the default
1089 ** VFS if makeDefault is non-zero.
1091 ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once
1094 int sqlite3_multiplex_initialize(const char *zOrigVfsName
, int makeDefault
){
1095 sqlite3_vfs
*pOrigVfs
;
1096 if( gMultiplex
.isInitialized
) return SQLITE_MISUSE
;
1097 pOrigVfs
= sqlite3_vfs_find(zOrigVfsName
);
1098 if( pOrigVfs
==0 ) return SQLITE_ERROR
;
1099 assert( pOrigVfs
!=&gMultiplex
.sThisVfs
);
1100 gMultiplex
.isInitialized
= 1;
1101 gMultiplex
.pOrigVfs
= pOrigVfs
;
1102 gMultiplex
.sThisVfs
= *pOrigVfs
;
1103 gMultiplex
.sThisVfs
.szOsFile
+= sizeof(multiplexConn
);
1104 gMultiplex
.sThisVfs
.zName
= SQLITE_MULTIPLEX_VFS_NAME
;
1105 gMultiplex
.sThisVfs
.xOpen
= multiplexOpen
;
1106 gMultiplex
.sThisVfs
.xDelete
= multiplexDelete
;
1107 gMultiplex
.sThisVfs
.xAccess
= multiplexAccess
;
1108 gMultiplex
.sThisVfs
.xFullPathname
= multiplexFullPathname
;
1109 gMultiplex
.sThisVfs
.xDlOpen
= multiplexDlOpen
;
1110 gMultiplex
.sThisVfs
.xDlError
= multiplexDlError
;
1111 gMultiplex
.sThisVfs
.xDlSym
= multiplexDlSym
;
1112 gMultiplex
.sThisVfs
.xDlClose
= multiplexDlClose
;
1113 gMultiplex
.sThisVfs
.xRandomness
= multiplexRandomness
;
1114 gMultiplex
.sThisVfs
.xSleep
= multiplexSleep
;
1115 gMultiplex
.sThisVfs
.xCurrentTime
= multiplexCurrentTime
;
1116 gMultiplex
.sThisVfs
.xGetLastError
= multiplexGetLastError
;
1117 gMultiplex
.sThisVfs
.xCurrentTimeInt64
= multiplexCurrentTimeInt64
;
1119 gMultiplex
.sIoMethodsV1
.iVersion
= 1;
1120 gMultiplex
.sIoMethodsV1
.xClose
= multiplexClose
;
1121 gMultiplex
.sIoMethodsV1
.xRead
= multiplexRead
;
1122 gMultiplex
.sIoMethodsV1
.xWrite
= multiplexWrite
;
1123 gMultiplex
.sIoMethodsV1
.xTruncate
= multiplexTruncate
;
1124 gMultiplex
.sIoMethodsV1
.xSync
= multiplexSync
;
1125 gMultiplex
.sIoMethodsV1
.xFileSize
= multiplexFileSize
;
1126 gMultiplex
.sIoMethodsV1
.xLock
= multiplexLock
;
1127 gMultiplex
.sIoMethodsV1
.xUnlock
= multiplexUnlock
;
1128 gMultiplex
.sIoMethodsV1
.xCheckReservedLock
= multiplexCheckReservedLock
;
1129 gMultiplex
.sIoMethodsV1
.xFileControl
= multiplexFileControl
;
1130 gMultiplex
.sIoMethodsV1
.xSectorSize
= multiplexSectorSize
;
1131 gMultiplex
.sIoMethodsV1
.xDeviceCharacteristics
=
1132 multiplexDeviceCharacteristics
;
1133 gMultiplex
.sIoMethodsV2
= gMultiplex
.sIoMethodsV1
;
1134 gMultiplex
.sIoMethodsV2
.iVersion
= 2;
1135 gMultiplex
.sIoMethodsV2
.xShmMap
= multiplexShmMap
;
1136 gMultiplex
.sIoMethodsV2
.xShmLock
= multiplexShmLock
;
1137 gMultiplex
.sIoMethodsV2
.xShmBarrier
= multiplexShmBarrier
;
1138 gMultiplex
.sIoMethodsV2
.xShmUnmap
= multiplexShmUnmap
;
1139 sqlite3_vfs_register(&gMultiplex
.sThisVfs
, makeDefault
);
1141 sqlite3_auto_extension((void(*)(void))multiplexFuncInit
);
1147 ** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown()
1149 ** All SQLite database connections must be closed before calling this
1152 ** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while
1153 ** shutting down in order to free all remaining multiplex groups.
1155 int sqlite3_multiplex_shutdown(int eForce
){
1157 if( gMultiplex
.isInitialized
==0 ) return SQLITE_MISUSE
;
1158 gMultiplex
.isInitialized
= 0;
1159 sqlite3_vfs_unregister(&gMultiplex
.sThisVfs
);
1160 memset(&gMultiplex
, 0, sizeof(gMultiplex
));
1164 /***************************** Test Code ***********************************/
1166 #if defined(INCLUDE_SQLITE_TCL_H)
1167 # include "sqlite_tcl.h"
1170 # ifndef SQLITE_TCLAPI
1171 # define SQLITE_TCLAPI
1174 extern const char *sqlite3ErrName(int);
1178 ** tclcmd: sqlite3_multiplex_initialize NAME MAKEDEFAULT
1180 static int SQLITE_TCLAPI
test_multiplex_initialize(
1184 Tcl_Obj
*CONST objv
[]
1186 const char *zName
; /* Name of new multiplex VFS */
1187 int makeDefault
; /* True to make the new VFS the default */
1188 int rc
; /* Value returned by multiplex_initialize() */
1190 UNUSED_PARAMETER(clientData
);
1192 /* Process arguments */
1194 Tcl_WrongNumArgs(interp
, 1, objv
, "NAME MAKEDEFAULT");
1197 zName
= Tcl_GetString(objv
[1]);
1198 if( Tcl_GetBooleanFromObj(interp
, objv
[2], &makeDefault
) ) return TCL_ERROR
;
1199 if( zName
[0]=='\0' ) zName
= 0;
1201 /* Call sqlite3_multiplex_initialize() */
1202 rc
= sqlite3_multiplex_initialize(zName
, makeDefault
);
1203 Tcl_SetResult(interp
, (char *)sqlite3ErrName(rc
), TCL_STATIC
);
1209 ** tclcmd: sqlite3_multiplex_shutdown
1211 static int SQLITE_TCLAPI
test_multiplex_shutdown(
1215 Tcl_Obj
*CONST objv
[]
1217 int rc
; /* Value returned by multiplex_shutdown() */
1219 UNUSED_PARAMETER(clientData
);
1221 if( objc
==2 && strcmp(Tcl_GetString(objv
[1]),"-force")!=0 ){
1224 if( (objc
!=1 && objc
!=2) ){
1225 Tcl_WrongNumArgs(interp
, 1, objv
, "?-force?");
1229 /* Call sqlite3_multiplex_shutdown() */
1230 rc
= sqlite3_multiplex_shutdown(objc
==2);
1231 Tcl_SetResult(interp
, (char *)sqlite3ErrName(rc
), TCL_STATIC
);
1237 ** Tclcmd: test_multiplex_control HANDLE DBNAME SUB-COMMAND ?INT-VALUE?
1239 static int SQLITE_TCLAPI
test_multiplex_control(
1243 Tcl_Obj
*CONST objv
[]
1245 int rc
; /* Return code from file_control() */
1246 int idx
; /* Index in aSub[] */
1247 Tcl_CmdInfo cmdInfo
; /* Command info structure for HANDLE */
1248 sqlite3
*db
; /* Underlying db handle for HANDLE */
1257 { "enable", MULTIPLEX_CTRL_ENABLE
, 1 },
1258 { "chunk_size", MULTIPLEX_CTRL_SET_CHUNK_SIZE
, 1 },
1259 { "max_chunks", MULTIPLEX_CTRL_SET_MAX_CHUNKS
, 1 },
1264 Tcl_WrongNumArgs(interp
, 1, objv
, "HANDLE DBNAME SUB-COMMAND INT-VALUE");
1268 if( 0==Tcl_GetCommandInfo(interp
, Tcl_GetString(objv
[1]), &cmdInfo
) ){
1269 Tcl_AppendResult(interp
, "expected database handle, got \"", 0);
1270 Tcl_AppendResult(interp
, Tcl_GetString(objv
[1]), "\"", 0);
1273 db
= *(sqlite3
**)cmdInfo
.objClientData
;
1276 rc
= Tcl_GetIndexFromObjStruct(
1277 interp
, objv
[3], aSub
, sizeof(aSub
[0]), "sub-command", 0, &idx
1279 if( rc
!=TCL_OK
) return rc
;
1281 switch( aSub
[idx
].argtype
){
1283 if( Tcl_GetIntFromObj(interp
, objv
[4], &iValue
) ){
1286 pArg
= (void *)&iValue
;
1289 Tcl_WrongNumArgs(interp
, 4, objv
, "SUB-COMMAND");
1293 rc
= sqlite3_file_control(db
, Tcl_GetString(objv
[2]), aSub
[idx
].op
, pArg
);
1294 Tcl_SetResult(interp
, (char *)sqlite3ErrName(rc
), TCL_STATIC
);
1295 return (rc
==SQLITE_OK
) ? TCL_OK
: TCL_ERROR
;
1299 ** This routine registers the custom TCL commands defined in this
1300 ** module. This should be the only procedure visible from outside
1303 int Sqlitemultiplex_Init(Tcl_Interp
*interp
){
1306 Tcl_ObjCmdProc
*xProc
;
1308 { "sqlite3_multiplex_initialize", test_multiplex_initialize
},
1309 { "sqlite3_multiplex_shutdown", test_multiplex_shutdown
},
1310 { "sqlite3_multiplex_control", test_multiplex_control
},
1314 for(i
=0; i
<sizeof(aCmd
)/sizeof(aCmd
[0]); i
++){
1315 Tcl_CreateObjCommand(interp
, aCmd
[i
].zName
, aCmd
[i
].xProc
, 0, 0);