Compare commits
196 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d55e8f5ff0 | |||
| 77428e6055 | |||
| 4853110e61 | |||
| f552a23740 | |||
| b93ce6d4a6 | |||
| 3eeda95d36 | |||
| 2cdce9c105 | |||
| 1f3762607d | |||
| 2da2a2698b | |||
| d080e84c81 | |||
| ff9e9b27d5 | |||
| 71fad44b38 | |||
| d445ed094a | |||
| 30db882a87 | |||
| ce909d1aae | |||
| 0f297b6e50 | |||
| 2d111c8bbb | |||
| 665f3d209b | |||
| e7f92d27ad | |||
| b031157128 | |||
| 8b832e24d4 | |||
| 8348abc3a2 | |||
| 1ac9aaf3fc | |||
| c5140b39ce | |||
| 6d537ca2d8 | |||
| 93543215e0 | |||
| e698c1f79e | |||
| 597bf19266 | |||
| eeb778d506 | |||
| 2c4328ed87 | |||
| f17e510ee5 | |||
| ca72570a11 | |||
| b0b27ab5c6 | |||
| d597b966d9 | |||
| 9c1eb40d69 | |||
| 254fa054ec | |||
| 7b812b6b16 | |||
| ff46e3f460 | |||
| c30d437376 | |||
| 079ae546fe | |||
| e05f922583 | |||
| 5330a3c6c9 | |||
| 6456b3929c | |||
| f8d7432aa3 | |||
| 7a13e692be | |||
| 50232dd821 | |||
| 4d8517e500 | |||
| 834c48c279 | |||
| 51883dfc8b | |||
| a63585eb8e | |||
| 816fc35956 | |||
| 8eed108df1 | |||
| dcb3992c66 | |||
| 2dd93c8838 | |||
| fccd5d43a9 | |||
| eb30704cc9 | |||
| 0c1fa5c8c6 | |||
| 4921c159bd | |||
| b7ee5667c2 | |||
| bfbe2b8c80 | |||
| 4af5f425f2 | |||
| 51abf049c7 | |||
| cdbf6d01a4 | |||
| 3a5fc00808 | |||
| 908803f5e0 | |||
| 699bd8121e | |||
| cb0267185c | |||
| 165ba4a2a5 | |||
| 8706485d1a | |||
| c0f69fcec7 | |||
| 6cbf8e173f | |||
| 2df2b3d05f | |||
| 40927fd61e | |||
| 50182fa846 | |||
| 5d122d308f | |||
| 288e98fb4b | |||
| bfcf8d6d67 | |||
| 24f1b25c7b | |||
| 3400e78f38 | |||
| 055cc1e431 | |||
| 7393c7424e | |||
| 3eba5d00b6 | |||
| b25057c291 | |||
| 9c54156a9c | |||
| d828d0debc | |||
| 6aab028db0 | |||
| 9f16a96d4b | |||
| 741802e825 | |||
| fd80ce4b10 | |||
| 9ee9b0f4c3 | |||
| c5856def1e | |||
| 0a2afca904 | |||
| 1af0a4e1df | |||
| 7365bcd72c | |||
| 1c6276eac6 | |||
| 827c9b785e | |||
| 4956bd5f9a | |||
| 63624a0ab8 | |||
| dc1bf28ad4 | |||
| d6396e9b16 | |||
| d4d01a689d | |||
| a096fcc73c | |||
| 50179f91f3 | |||
| 35dcd0e892 | |||
| 7f002db7e3 | |||
| 995b2457e2 | |||
| 7fff2e1cb9 | |||
| e3b047b3d7 | |||
| 7037787d30 | |||
| 606f718480 | |||
| e3c3be8511 | |||
| aa59505ae8 | |||
| 55b36d5c07 | |||
| 6dbb452b38 | |||
| 2ecf1ec8c9 | |||
| 9a89254e7a | |||
| fe329a7370 | |||
| 4d55d35df6 | |||
| 246e1d8faf | |||
| 0195d8ef5d | |||
| e32c7c2b64 | |||
| 0ba9725489 | |||
| ff8e42e2ca | |||
| a8dee8df64 | |||
| 7d931b985a | |||
| 8bbf544747 | |||
| a7d16a5cd1 | |||
| c18d304066 | |||
| 3d4ad18cbe | |||
| 21ac1de75b | |||
| 96b9dc9b36 | |||
| adeb47e57b | |||
| 12891cc492 | |||
| 820fc4933b | |||
| b00083429a | |||
| b0b18f6dca | |||
| 76cb74da4d | |||
| db454d72b6 | |||
| 99b14894eb | |||
| 03206c433c | |||
| 93f88ebbb1 | |||
| faf010d77a | |||
| 7c9ecc62c5 | |||
| 04b9cf1e7e | |||
| f687ba597f | |||
| 76ff0699bb | |||
| 607d4c2184 | |||
| 8c9ea487d0 | |||
| 1d9497a0d0 | |||
| 31441fb50b | |||
| f12cc2a68c | |||
| cd9491ca2f | |||
| e8a1ea6d8b | |||
| 0faad42d59 | |||
| 411547ec6c | |||
| 9a5a469c7a | |||
| 573ebef187 | |||
| 144cd3df05 | |||
| 3561ec2ede | |||
| 01be463eeb | |||
| a62b6bd93c | |||
| f56af5ee2d | |||
| 3f531da564 | |||
| 7b8996c878 | |||
| c8a9d15887 | |||
| 6b3e51bd33 | |||
| 987f821f79 | |||
| f6cf5ea790 | |||
| de36c76a23 | |||
| fef3410f7f | |||
| 57888f7300 | |||
| 4073b26a20 | |||
| ac0a42233a | |||
| 3c40ed4281 | |||
| 0cd4f69c0c | |||
| bf3cf57e15 | |||
| f5e89dba9d | |||
| 0408529b48 | |||
| 5cf03728a6 | |||
| 8b994f29a5 | |||
| 654a965367 | |||
| 0c52b373a0 | |||
| f5cebf71fe | |||
| c299a11c02 | |||
| 8c5847a257 | |||
| b87b25f219 | |||
| 572a21c8e9 | |||
| 70af25d03d | |||
| cec0beebb3 | |||
| 64b310ed1a | |||
| 699bdf056b | |||
| 7b3d71e9cc | |||
| 1a9cde3ba9 | |||
| 773d2d6c09 | |||
| 37d36205f3 | |||
| 3d39434c36 |
@@ -0,0 +1,107 @@
|
||||
|
||||
Begin Concurrent
|
||||
================
|
||||
|
||||
## Overview
|
||||
|
||||
Usually, SQLite allows at most one writer to proceed concurrently. The
|
||||
BEGIN CONCURRENT enhancement allows multiple writers to process write
|
||||
transactions simultanously if the database is in "wal" or "wal2" mode,
|
||||
although the system still serializes COMMIT commands.
|
||||
|
||||
When a write-transaction is opened with "BEGIN CONCURRENT", actually
|
||||
locking the database is deferred until a COMMIT is executed. This means
|
||||
that any number of transactions started with BEGIN CONCURRENT may proceed
|
||||
concurrently. The system uses optimistic page-level-locking to prevent
|
||||
conflicting concurrent transactions from being committed.
|
||||
|
||||
When a BEGIN CONCURRENT transaction is committed, the system checks whether
|
||||
or not any of the database pages that the transaction has read have been
|
||||
modified since the BEGIN CONCURRENT was opened. In other words - it asks
|
||||
if the transaction being committed operates on a different set of data than
|
||||
all other concurrently executing transactions. If the answer is "yes, this
|
||||
transaction did not read or modify any data modified by any concurrent
|
||||
transaction", then the transaction is committed as normal. Otherwise, if the
|
||||
transaction does conflict, it cannot be committed and an SQLITE_BUSY_SNAPSHOT
|
||||
error is returned. At this point, all the client can do is ROLLBACK the
|
||||
transaction.
|
||||
|
||||
If SQLITE_BUSY_SNAPSHOT is returned, messages are output via the sqlite3_log
|
||||
mechanism indicating the page and table or index on which the conflict
|
||||
occurred. This can be useful when optimizing concurrency.
|
||||
|
||||
## Application Programming Notes
|
||||
|
||||
In order to serialize COMMIT processing, SQLite takes a lock on the database
|
||||
as part of each COMMIT command and releases it before returning. At most one
|
||||
writer may hold this lock at any one time. If a writer cannot obtain the lock,
|
||||
it uses SQLite's busy-handler to pause and retry for a while:
|
||||
|
||||
<a href=https://www.sqlite.org/c3ref/busy_handler.html>
|
||||
https://www.sqlite.org/c3ref/busy_handler.html
|
||||
</a>
|
||||
|
||||
If there is significant contention for the writer lock, this mechanism can be
|
||||
inefficient. In this case it is better for the application to use a mutex or
|
||||
some other mechanism that supports blocking to ensure that at most one writer
|
||||
is attempting to COMMIT a BEGIN CONCURRENT transaction at a time. This is
|
||||
usually easier if all writers are part of the same operating system process.
|
||||
|
||||
If all database clients (readers and writers) are located in the same OS
|
||||
process, and if that OS is a Unix variant, then it can be more efficient to
|
||||
the built-in VFS "unix-excl" instead of the default "unix". This is because it
|
||||
uses more efficient locking primitives.
|
||||
|
||||
The key to maximizing concurrency using BEGIN CONCURRENT is to ensure that
|
||||
there are a large number of non-conflicting transactions. In SQLite, each
|
||||
table and each index is stored as a separate b-tree, each of which is
|
||||
distributed over a discrete set of database pages. This means that:
|
||||
|
||||
* Two transactions that write to different sets of tables never
|
||||
conflict, and that
|
||||
|
||||
* Two transactions that write to the same tables or indexes only
|
||||
conflict if the values of the keys (either primary keys or indexed
|
||||
rows) are fairly close together. For example, given a large
|
||||
table with the schema:
|
||||
|
||||
<pre> CREATE TABLE t1(a INTEGER PRIMARY KEY, b BLOB);</pre>
|
||||
|
||||
writing two rows with adjacent values for "a" probably will cause a
|
||||
conflict (as the two keys are stored on the same page), but writing two
|
||||
rows with vastly different values for "a" will not (as the keys will likly
|
||||
be stored on different pages).
|
||||
|
||||
Note that, in SQLite, if values are not explicitly supplied for an INTEGER
|
||||
PRIMARY KEY, as for example in:
|
||||
|
||||
>
|
||||
INSERT INTO t1(b) VALUES(<blob-value>);
|
||||
|
||||
then monotonically increasing values are assigned automatically. This is
|
||||
terrible for concurrency, as it all but ensures that all new rows are
|
||||
added to the same database page. In such situations, it is better to
|
||||
explicitly assign random values to INTEGER PRIMARY KEY fields.
|
||||
|
||||
This problem also comes up for non-WITHOUT ROWID tables that do not have an
|
||||
explicit INTEGER PRIMARY KEY column. In these cases each table has an implicit
|
||||
INTEGER PRIMARY KEY column that is assigned increasing values, leading to the
|
||||
same problem as omitting to assign a value to an explicit INTEGER PRIMARY KEY
|
||||
column.
|
||||
|
||||
For both explicit and implicit INTEGER PRIMARY KEYs, it is possible to have
|
||||
SQLite assign values at random (instead of the monotonically increasing
|
||||
values) by writing a row with a rowid equal to the largest possible signed
|
||||
64-bit integer to the table. For example:
|
||||
|
||||
INSERT INTO t1(a) VALUES(9223372036854775807);
|
||||
|
||||
Applications should take care not to malfunction due to the presence of such
|
||||
rows.
|
||||
|
||||
The nature of some types of indexes, for example indexes on timestamp fields,
|
||||
can also cause problems (as concurrent transactions may assign similar
|
||||
timestamps that will be stored on the same db page to new records). In these
|
||||
cases the database schema may need to be rethought to increase the concurrency
|
||||
provided by page-level-locking.
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
|
||||
Wal2 Mode Notes
|
||||
===============
|
||||
|
||||
## Activating/Deactivating Wal2 Mode
|
||||
|
||||
"Wal2" mode is very similar to "wal" mode. To change a database to wal2 mode,
|
||||
use the command:
|
||||
|
||||
>
|
||||
PRAGMA journal_mode = wal2;
|
||||
|
||||
It is not possible to change a database directly from "wal" mode to "wal2"
|
||||
mode. Instead, it must first be changed to rollback mode. So, to change a wal
|
||||
mode database to wal2 mode, the following two commands may be used:
|
||||
|
||||
>
|
||||
PRAGMA journal_mode = delete;
|
||||
PRAGMA journal_mode = wal2;
|
||||
|
||||
A database in wal2 mode may only be accessed by versions of SQLite compiled
|
||||
from this branch. Attempting to use any other version of SQLite results in an
|
||||
SQLITE_NOTADB error. A wal2 mode database may be changed back to rollback mode
|
||||
(making it accessible by all versions of SQLite) using:
|
||||
|
||||
>
|
||||
PRAGMA journal_mode = delete;
|
||||
|
||||
## The Advantage of Wal2 Mode
|
||||
|
||||
In legacy wal mode, when a writer writes data to the database, it doesn't
|
||||
modify the database file directly. Instead, it appends new data to the
|
||||
"<database>-wal" file. Readers read data from both the original database
|
||||
file and the "<database>-wal" file. At some point, data is copied from the
|
||||
"<database>-wal" file into the database file, after which the wal file can
|
||||
be deleted or overwritten. Copying data from the wal file into the database
|
||||
file is called a "checkpoint", and may be done explictly (either by "PRAGMA
|
||||
wal_checkpoint" or sqlite3_wal_checkpoint_v2()), or
|
||||
automatically (by configuring "PRAGMA wal_autocheckpoint" - this is the
|
||||
default).
|
||||
|
||||
Checkpointers do not block writers, and writers do not block checkpointers.
|
||||
However, if a writer writes to the database while a checkpoint is ongoing,
|
||||
then the new data is appended to the end of the wal file. This means that,
|
||||
even following the checkpoint, the wal file cannot be overwritten or deleted,
|
||||
and so all subsequent transactions must also be appended to the wal file. The
|
||||
work of the checkpointer is not wasted - SQLite remembers which parts of the
|
||||
wal file have already been copied into the db file so that the next checkpoint
|
||||
does not have to do so again - but it does mean that the wal file may grow
|
||||
indefinitely if the checkpointer never gets a chance to finish without a
|
||||
writer appending to the wal file. There are also circumstances in which
|
||||
long-running readers may prevent a checkpointer from checkpointing the entire
|
||||
wal file - also causing the wal file to grow indefinitely in a busy system.
|
||||
|
||||
Wal2 mode does not have this problem. In wal2 mode, wal files do not grow
|
||||
indefinitely even if the checkpointer never has a chance to finish
|
||||
uninterrupted.
|
||||
|
||||
In wal2 mode, the system uses two wal files instead of one. The files are named
|
||||
"<database>-wal" and "<database>-wal2", where "<database>" is of
|
||||
course the name of the database file. When data is written to the database, the
|
||||
writer begins by appending the new data to the first wal file. Once the first
|
||||
wal file has grown large enough, writers switch to appending data to the second
|
||||
wal file. At this point the first wal file can be checkpointed (after which it
|
||||
can be overwritten). Then, once the second wal file has grown large enough and
|
||||
the first wal file has been checkpointed, writers switch back to the first wal
|
||||
file. And so on.
|
||||
|
||||
## Application Programming
|
||||
|
||||
From the point of view of the user, the main differences between wal and
|
||||
wal2 mode are to do with checkpointing:
|
||||
|
||||
* In wal mode, a checkpoint may be attempted at any time. In wal2
|
||||
mode, the checkpointer has to wait until writers have switched
|
||||
to the "other" wal file before a checkpoint can take place.
|
||||
|
||||
* In wal mode, the wal-hook (callback registered using
|
||||
sqlite3_wal_hook()) is invoked after a transaction is committed
|
||||
with the total number of pages in the wal file as an argument. In wal2
|
||||
mode, the argument is either the total number of uncheckpointed pages in
|
||||
both wal files, or - if the "other" wal file is empty or already
|
||||
checkpointed - 0.
|
||||
|
||||
Clients are recommended to use the same strategies for checkpointing wal2 mode
|
||||
databases as for wal databases - by registering a wal-hook using
|
||||
sqlite3_wal_hook() and attempting a checkpoint when the parameter
|
||||
exceeds a certain threshold.
|
||||
|
||||
However, it should be noted that although the wal-hook is invoked after each
|
||||
transaction is committed to disk and database locks released, it is still
|
||||
invoked from within the sqlite3_step() call used to execute the "COMMIT"
|
||||
command. In BEGIN CONCURRENT systems, where the "COMMIT" is often protected by
|
||||
an application mutex, this may reduce concurrency. In such systems, instead of
|
||||
executing a checkpoint from within the wal-hook, a thread might defer this
|
||||
action until after the application mutex has been released.
|
||||
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
** 2017-10-11
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
*/
|
||||
|
||||
#if !defined(SQLITE_TEST) || defined(SQLITE_OS_UNIX)
|
||||
|
||||
#include "sqlite3.h"
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/*
|
||||
** API declarations.
|
||||
*/
|
||||
typedef struct Checkpointer Checkpointer;
|
||||
int sqlite3_bgckpt_create(const char *zFilename, Checkpointer **pp);
|
||||
int sqlite3_bgckpt_checkpoint(Checkpointer *p, int bBlock);
|
||||
void sqlite3_bgckpt_destroy(Checkpointer *p);
|
||||
|
||||
|
||||
struct Checkpointer {
|
||||
sqlite3 *db; /* Database handle */
|
||||
|
||||
pthread_t thread; /* Background thread */
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
|
||||
int rc; /* Error from "PRAGMA wal_checkpoint" */
|
||||
int bCkpt; /* True if checkpoint requested */
|
||||
int bExit; /* True if exit requested */
|
||||
};
|
||||
|
||||
static void *bgckptThreadMain(void *pCtx){
|
||||
int rc = SQLITE_OK;
|
||||
Checkpointer *p = (Checkpointer*)pCtx;
|
||||
|
||||
while( rc==SQLITE_OK ){
|
||||
int bExit;
|
||||
|
||||
pthread_mutex_lock(&p->mutex);
|
||||
if( p->bCkpt==0 && p->bExit==0 ){
|
||||
pthread_cond_wait(&p->cond, &p->mutex);
|
||||
}
|
||||
p->bCkpt = 0;
|
||||
bExit = p->bExit;
|
||||
pthread_mutex_unlock(&p->mutex);
|
||||
|
||||
if( bExit ) break;
|
||||
rc = sqlite3_exec(p->db, "PRAGMA wal_checkpoint", 0, 0, 0);
|
||||
if( rc==SQLITE_BUSY ){
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&p->mutex);
|
||||
p->rc = rc;
|
||||
pthread_mutex_unlock(&p->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sqlite3_bgckpt_destroy(Checkpointer *p){
|
||||
if( p ){
|
||||
void *ret = 0;
|
||||
|
||||
/* Signal the background thread to exit */
|
||||
pthread_mutex_lock(&p->mutex);
|
||||
p->bExit = 1;
|
||||
pthread_cond_broadcast(&p->cond);
|
||||
pthread_mutex_unlock(&p->mutex);
|
||||
|
||||
pthread_join(p->thread, &ret);
|
||||
sqlite3_close(p->db);
|
||||
sqlite3_free(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int sqlite3_bgckpt_create(const char *zFilename, Checkpointer **pp){
|
||||
Checkpointer *pNew = 0;
|
||||
int rc;
|
||||
|
||||
pNew = (Checkpointer*)sqlite3_malloc(sizeof(Checkpointer));
|
||||
if( pNew==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
memset(pNew, 0, sizeof(Checkpointer));
|
||||
rc = sqlite3_open(zFilename, &pNew->db);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
pthread_mutex_init(&pNew->mutex, 0);
|
||||
pthread_cond_init(&pNew->cond, 0);
|
||||
pthread_create(&pNew->thread, 0, bgckptThreadMain, (void*)pNew);
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_bgckpt_destroy(pNew);
|
||||
pNew = 0;
|
||||
}
|
||||
*pp = pNew;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int sqlite3_bgckpt_checkpoint(Checkpointer *p, int bBlock){
|
||||
int rc;
|
||||
pthread_mutex_lock(&p->mutex);
|
||||
rc = p->rc;
|
||||
if( rc==SQLITE_OK ){
|
||||
p->bCkpt = 1;
|
||||
pthread_cond_broadcast(&p->cond);
|
||||
}
|
||||
pthread_mutex_unlock(&p->mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
|
||||
#if defined(INCLUDE_SQLITE_TCL_H)
|
||||
# include "sqlite_tcl.h"
|
||||
#else
|
||||
# include "tcl.h"
|
||||
# ifndef SQLITE_TCLAPI
|
||||
# define SQLITE_TCLAPI
|
||||
# endif
|
||||
#endif
|
||||
|
||||
const char *sqlite3ErrName(int rc);
|
||||
|
||||
static void SQLITE_TCLAPI bgckpt_del(void * clientData){
|
||||
Checkpointer *pCkpt = (Checkpointer*)clientData;
|
||||
sqlite3_bgckpt_destroy(pCkpt);
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: $ckpt SUBCMD ...
|
||||
*/
|
||||
static int SQLITE_TCLAPI bgckpt_obj_cmd(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
Checkpointer *pCkpt = (Checkpointer*)clientData;
|
||||
const char *aCmd[] = { "checkpoint", "destroy", 0 };
|
||||
int iCmd;
|
||||
|
||||
if( objc<2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "SUBCMD ...");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( Tcl_GetIndexFromObj(interp, objv[1], aCmd, "sub-command", 0, &iCmd) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
switch( iCmd ){
|
||||
case 0: {
|
||||
int rc;
|
||||
int bBlock = 0;
|
||||
|
||||
if( objc>3 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "?BLOCKING?");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( objc==3 && Tcl_GetBooleanFromObj(interp, objv[2], &bBlock) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
rc = sqlite3_bgckpt_checkpoint(pCkpt, bBlock);
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
return TCL_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: bgckpt CMDNAME FILENAME
|
||||
*/
|
||||
static int SQLITE_TCLAPI bgckpt_cmd(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
const char *zCmd;
|
||||
const char *zFilename;
|
||||
int rc;
|
||||
Checkpointer *pCkpt;
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "CMDNAME FILENAME");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
zCmd = Tcl_GetString(objv[1]);
|
||||
zFilename = Tcl_GetString(objv[2]);
|
||||
|
||||
rc = sqlite3_bgckpt_create(zFilename, &pCkpt);
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
Tcl_CreateObjCommand(interp, zCmd, bgckpt_obj_cmd, (void*)pCkpt, bgckpt_del);
|
||||
Tcl_SetObjResult(interp, objv[1]);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
int Bgckpt_Init(Tcl_Interp *interp){
|
||||
Tcl_CreateObjCommand(interp, "bgckpt", bgckpt_cmd, 0, 0);
|
||||
return TCL_OK;
|
||||
}
|
||||
#endif /* SQLITE_TEST */
|
||||
|
||||
#else
|
||||
#if defined(INCLUDE_SQLITE_TCL_H)
|
||||
# include "sqlite_tcl.h"
|
||||
#else
|
||||
# include "tcl.h"
|
||||
# ifndef SQLITE_TCLAPI
|
||||
# define SQLITE_TCLAPI
|
||||
# endif
|
||||
#endif
|
||||
int Bgckpt_Init(Tcl_Interp *interp){ return TCL_OK; }
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
# 2016 August 23
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
|
||||
set testprefix changebatch1
|
||||
|
||||
|
||||
proc sql_to_changeset {method sql} {
|
||||
sqlite3session S db main
|
||||
S attach *
|
||||
execsql $sql
|
||||
set ret [S $method]
|
||||
S delete
|
||||
return $ret
|
||||
}
|
||||
|
||||
proc do_changebatch_test {tn method args} {
|
||||
set C [list]
|
||||
foreach a $args {
|
||||
lappend C [sql_to_changeset $method $a]
|
||||
}
|
||||
|
||||
sqlite3changebatch cb db
|
||||
set i 1
|
||||
foreach ::cs [lrange $C 0 end-1] {
|
||||
set rc [cb add $::cs]
|
||||
if {$rc!="SQLITE_OK"} { error "expected SQLITE_OK, got $rc (i=$i)" }
|
||||
incr i
|
||||
}
|
||||
|
||||
set ::cs [lindex $C end]
|
||||
do_test $tn { cb add [set ::cs] } SQLITE_CONSTRAINT
|
||||
cb delete
|
||||
}
|
||||
|
||||
proc do_changebatch_test1 {tn args} {
|
||||
uplevel do_changebatch_test $tn changeset $args
|
||||
}
|
||||
proc do_changebatch_test2 {tn args} {
|
||||
uplevel do_changebatch_test $tn fullchangeset $args
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# The body of the following loop contains tests for database schemas
|
||||
# that do not feature multi-column UNIQUE constraints. In this case
|
||||
# it doesn't matter if the changesets are generated using
|
||||
# sqlite3session_changeset() or sqlite3session_fullchangeset().
|
||||
#
|
||||
foreach {tn testfunction} {
|
||||
1 do_changebatch_test1
|
||||
2 do_changebatch_test2
|
||||
} {
|
||||
reset_db
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_execsql_test $tn.1.0 {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
}
|
||||
|
||||
$testfunction $tn.1.1 {
|
||||
INSERT INTO t1 VALUES(1, 1);
|
||||
} {
|
||||
DELETE FROM t1 WHERE a=1;
|
||||
}
|
||||
|
||||
do_execsql_test $tn.1.2.0 {
|
||||
INSERT INTO t1 VALUES(1, 1);
|
||||
INSERT INTO t1 VALUES(2, 2);
|
||||
INSERT INTO t1 VALUES(3, 3);
|
||||
}
|
||||
$testfunction $tn.1.2.1 {
|
||||
DELETE FROM t1 WHERE a=2;
|
||||
} {
|
||||
INSERT INTO t1 VALUES(2, 2);
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_execsql_test $tn.2.0 {
|
||||
CREATE TABLE x1(a, b PRIMARY KEY, c UNIQUE);
|
||||
CREATE TABLE x2(a PRIMARY KEY, b UNIQUE, c UNIQUE);
|
||||
CREATE INDEX x1a ON x1(a);
|
||||
|
||||
INSERT INTO x1 VALUES(1, 1, 'a');
|
||||
INSERT INTO x1 VALUES(1, 2, 'b');
|
||||
INSERT INTO x1 VALUES(1, 3, 'c');
|
||||
}
|
||||
|
||||
$testfunction $tn.2.1 {
|
||||
DELETE FROM x1 WHERE b=2;
|
||||
} {
|
||||
UPDATE x1 SET c='b' WHERE b=3;
|
||||
}
|
||||
|
||||
$testfunction $tn.2.2 {
|
||||
DELETE FROM x1 WHERE b=1;
|
||||
} {
|
||||
INSERT INTO x1 VALUES(1, 5, 'a');
|
||||
}
|
||||
|
||||
set L [list]
|
||||
for {set i 1000} {$i < 10000} {incr i} {
|
||||
lappend L "INSERT INTO x2 VALUES($i, $i, 'x' || $i)"
|
||||
}
|
||||
lappend L "DELETE FROM x2 WHERE b=1005"
|
||||
$testfunction $tn.2.3 {*}$L
|
||||
|
||||
execsql { INSERT INTO x1 VALUES('f', 'f', 'f') }
|
||||
$testfunction $tn.2.4 {
|
||||
INSERT INTO x2 VALUES('f', 'f', 'f');
|
||||
} {
|
||||
INSERT INTO x1 VALUES('g', 'g', 'g');
|
||||
} {
|
||||
DELETE FROM x1 WHERE b='f';
|
||||
} {
|
||||
INSERT INTO x2 VALUES('g', 'g', 'g');
|
||||
} {
|
||||
INSERT INTO x1 VALUES('f', 'f', 'f');
|
||||
}
|
||||
|
||||
execsql {
|
||||
DELETE FROM x1;
|
||||
INSERT INTO x1 VALUES(1.5, 1.5, 1.5);
|
||||
}
|
||||
$testfunction $tn.2.5 {
|
||||
DELETE FROM x1 WHERE b BETWEEN 1 AND 2;
|
||||
} {
|
||||
INSERT INTO x1 VALUES(2.5, 2.5, 2.5);
|
||||
} {
|
||||
INSERT INTO x1 VALUES(1.5, 1.5, 1.5);
|
||||
}
|
||||
|
||||
execsql {
|
||||
DELETE FROM x2;
|
||||
INSERT INTO x2 VALUES(X'abcd', X'1234', X'7890');
|
||||
INSERT INTO x2 VALUES(X'0000', X'0000', X'0000');
|
||||
}
|
||||
breakpoint
|
||||
$testfunction $tn.2.6 {
|
||||
UPDATE x2 SET c = X'1234' WHERE a=X'abcd';
|
||||
INSERT INTO x2 VALUES(X'1234', X'abcd', X'7890');
|
||||
} {
|
||||
DELETE FROM x2 WHERE b=X'0000';
|
||||
} {
|
||||
INSERT INTO x2 VALUES(1, X'0000', NULL);
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test some multi-column UNIQUE constraints. First Using _changeset() to
|
||||
# demonstrate the problem, then using _fullchangeset() to show that it has
|
||||
# been fixed.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE y1(a PRIMARY KEY, b, c, UNIQUE(b, c));
|
||||
INSERT INTO y1 VALUES(1, 1, 1);
|
||||
INSERT INTO y1 VALUES(2, 2, 2);
|
||||
INSERT INTO y1 VALUES(3, 3, 3);
|
||||
INSERT INTO y1 VALUES(4, 3, 4);
|
||||
BEGIN;
|
||||
}
|
||||
|
||||
do_test 3.1.1 {
|
||||
set c1 [sql_to_changeset changeset { DELETE FROM y1 WHERE a=4 }]
|
||||
set c2 [sql_to_changeset changeset { UPDATE y1 SET c=4 WHERE a=3 }]
|
||||
sqlite3changebatch cb db
|
||||
cb add $c1
|
||||
cb add $c2
|
||||
} {SQLITE_OK}
|
||||
do_test 3.1.2 {
|
||||
cb delete
|
||||
execsql ROLLBACK
|
||||
} {}
|
||||
|
||||
do_test 3.1.1 {
|
||||
set c1 [sql_to_changeset fullchangeset { DELETE FROM y1 WHERE a=4 }]
|
||||
set c2 [sql_to_changeset fullchangeset { UPDATE y1 SET c=4 WHERE a=3 }]
|
||||
sqlite3changebatch cb db
|
||||
cb add $c1
|
||||
cb add $c2
|
||||
} {SQLITE_CONSTRAINT}
|
||||
do_test 3.1.2 {
|
||||
cb delete
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 4.0 {
|
||||
CREATE TABLE t1(x, y, z, PRIMARY KEY(x, y), UNIQUE(z));
|
||||
}
|
||||
|
||||
do_test 4.1 {
|
||||
set c1 [sql_to_changeset fullchangeset { INSERT INTO t1 VALUES(1, 2, 3) }]
|
||||
execsql {
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1(w, x, y, z, PRIMARY KEY(x, y), UNIQUE(z));
|
||||
}
|
||||
sqlite3changebatch cb db
|
||||
list [catch { cb add $c1 } msg] $msg
|
||||
} {1 SQLITE_RANGE}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,42 @@
|
||||
# 2011 Mar 21
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# The focus of this file is testing the session module.
|
||||
#
|
||||
|
||||
if {![info exists testdir]} {
|
||||
set testdir [file join [file dirname [info script]] .. .. test]
|
||||
}
|
||||
source [file join [file dirname [info script]] session_common.tcl]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable !session {finish_test; return}
|
||||
set testprefix changebatchfault
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b, c PRIMARY KEY, UNIQUE(a, b));
|
||||
INSERT INTO t1 VALUES('a', 'a', 'a');
|
||||
INSERT INTO t1 VALUES('b', 'b', 'b');
|
||||
}
|
||||
|
||||
set ::c1 [changeset_from_sql { delete from t1 where c='a' }]
|
||||
set ::c2 [changeset_from_sql { insert into t1 values('c', 'c', 'c') }]
|
||||
|
||||
do_faultsim_test 1 -faults oom-* -body {
|
||||
sqlite3changebatch cb db
|
||||
cb add $::c1
|
||||
cb add $::c2
|
||||
} -test {
|
||||
faultsim_test_result {0 SQLITE_OK} {1 SQLITE_NOMEM}
|
||||
catch { cb delete }
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,485 @@
|
||||
|
||||
#if !defined(SQLITE_TEST) || (defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK))
|
||||
|
||||
#include "sqlite3session.h"
|
||||
#include "sqlite3changebatch.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct BatchTable BatchTable;
|
||||
typedef struct BatchIndex BatchIndex;
|
||||
typedef struct BatchIndexEntry BatchIndexEntry;
|
||||
typedef struct BatchHash BatchHash;
|
||||
|
||||
struct sqlite3_changebatch {
|
||||
sqlite3 *db; /* Database handle used to read schema */
|
||||
BatchTable *pTab; /* First in linked list of tables */
|
||||
int iChangesetId; /* Current changeset id */
|
||||
int iNextIdxId; /* Next available index id */
|
||||
int nEntry; /* Number of entries in hash table */
|
||||
int nHash; /* Number of hash buckets */
|
||||
BatchIndexEntry **apHash; /* Array of hash buckets */
|
||||
};
|
||||
|
||||
struct BatchTable {
|
||||
BatchIndex *pIdx; /* First in linked list of UNIQUE indexes */
|
||||
BatchTable *pNext; /* Next table */
|
||||
char zTab[1]; /* Table name */
|
||||
};
|
||||
|
||||
struct BatchIndex {
|
||||
BatchIndex *pNext; /* Next index on same table */
|
||||
int iId; /* Index id (assigned internally) */
|
||||
int bPk; /* True for PK index */
|
||||
int nCol; /* Size of aiCol[] array */
|
||||
int *aiCol; /* Array of columns that make up index */
|
||||
};
|
||||
|
||||
struct BatchIndexEntry {
|
||||
BatchIndexEntry *pNext; /* Next colliding hash table entry */
|
||||
int iChangesetId; /* Id of associated changeset */
|
||||
int iIdxId; /* Id of index this key is from */
|
||||
int szRecord;
|
||||
char aRecord[1];
|
||||
};
|
||||
|
||||
/*
|
||||
** Allocate and zero a block of nByte bytes. Must be freed using cbFree().
|
||||
*/
|
||||
static void *cbMalloc(int *pRc, int nByte){
|
||||
void *pRet;
|
||||
|
||||
if( *pRc ){
|
||||
pRet = 0;
|
||||
}else{
|
||||
pRet = sqlite3_malloc(nByte);
|
||||
if( pRet ){
|
||||
memset(pRet, 0, nByte);
|
||||
}else{
|
||||
*pRc = SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Free an allocation made by cbMalloc().
|
||||
*/
|
||||
static void cbFree(void *p){
|
||||
sqlite3_free(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the hash bucket that pEntry belongs in.
|
||||
*/
|
||||
static int cbHash(sqlite3_changebatch *p, BatchIndexEntry *pEntry){
|
||||
unsigned int iHash = (unsigned int)pEntry->iIdxId;
|
||||
unsigned char *pEnd = (unsigned char*)&pEntry->aRecord[pEntry->szRecord];
|
||||
unsigned char *pIter;
|
||||
|
||||
for(pIter=(unsigned char*)pEntry->aRecord; pIter<pEnd; pIter++){
|
||||
iHash += (iHash << 7) + *pIter;
|
||||
}
|
||||
|
||||
return (int)(iHash % p->nHash);
|
||||
}
|
||||
|
||||
/*
|
||||
** Resize the hash table.
|
||||
*/
|
||||
static int cbHashResize(sqlite3_changebatch *p){
|
||||
int rc = SQLITE_OK;
|
||||
BatchIndexEntry **apNew;
|
||||
int nNew = (p->nHash ? p->nHash*2 : 512);
|
||||
int i;
|
||||
|
||||
apNew = cbMalloc(&rc, sizeof(BatchIndexEntry*) * nNew);
|
||||
if( rc==SQLITE_OK ){
|
||||
int nHash = p->nHash;
|
||||
p->nHash = nNew;
|
||||
for(i=0; i<nHash; i++){
|
||||
BatchIndexEntry *pEntry;
|
||||
while( (pEntry=p->apHash[i])!=0 ){
|
||||
int iHash = cbHash(p, pEntry);
|
||||
p->apHash[i] = pEntry->pNext;
|
||||
pEntry->pNext = apNew[iHash];
|
||||
apNew[iHash] = pEntry;
|
||||
}
|
||||
}
|
||||
|
||||
cbFree(p->apHash);
|
||||
p->apHash = apNew;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Allocate a new sqlite3_changebatch object.
|
||||
*/
|
||||
int sqlite3changebatch_new(sqlite3 *db, sqlite3_changebatch **pp){
|
||||
sqlite3_changebatch *pRet;
|
||||
int rc = SQLITE_OK;
|
||||
*pp = pRet = (sqlite3_changebatch*)cbMalloc(&rc, sizeof(sqlite3_changebatch));
|
||||
if( pRet ){
|
||||
pRet->db = db;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Add a BatchIndex entry for index zIdx to table pTab.
|
||||
*/
|
||||
static int cbAddIndex(
|
||||
sqlite3_changebatch *p,
|
||||
BatchTable *pTab,
|
||||
const char *zIdx,
|
||||
int bPk
|
||||
){
|
||||
int nCol = 0;
|
||||
sqlite3_stmt *pIndexInfo = 0;
|
||||
BatchIndex *pNew = 0;
|
||||
int rc;
|
||||
char *zIndexInfo;
|
||||
|
||||
zIndexInfo = (char*)sqlite3_mprintf("PRAGMA main.index_info = %Q", zIdx);
|
||||
if( zIndexInfo ){
|
||||
rc = sqlite3_prepare_v2(p->db, zIndexInfo, -1, &pIndexInfo, 0);
|
||||
sqlite3_free(zIndexInfo);
|
||||
}else{
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
while( SQLITE_ROW==sqlite3_step(pIndexInfo) ){ nCol++; }
|
||||
rc = sqlite3_reset(pIndexInfo);
|
||||
}
|
||||
|
||||
pNew = (BatchIndex*)cbMalloc(&rc, sizeof(BatchIndex) + sizeof(int) * nCol);
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew->nCol = nCol;
|
||||
pNew->bPk = bPk;
|
||||
pNew->aiCol = (int*)&pNew[1];
|
||||
pNew->iId = p->iNextIdxId++;
|
||||
while( SQLITE_ROW==sqlite3_step(pIndexInfo) ){
|
||||
int i = sqlite3_column_int(pIndexInfo, 0);
|
||||
int j = sqlite3_column_int(pIndexInfo, 1);
|
||||
pNew->aiCol[i] = j;
|
||||
}
|
||||
rc = sqlite3_reset(pIndexInfo);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew->pNext = pTab->pIdx;
|
||||
pTab->pIdx = pNew;
|
||||
}else{
|
||||
cbFree(pNew);
|
||||
}
|
||||
sqlite3_finalize(pIndexInfo);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Free the object passed as the first argument.
|
||||
*/
|
||||
static void cbFreeTable(BatchTable *pTab){
|
||||
BatchIndex *pIdx;
|
||||
BatchIndex *pIdxNext;
|
||||
for(pIdx=pTab->pIdx; pIdx; pIdx=pIdxNext){
|
||||
pIdxNext = pIdx->pNext;
|
||||
cbFree(pIdx);
|
||||
}
|
||||
cbFree(pTab);
|
||||
}
|
||||
|
||||
/*
|
||||
** Find or create the BatchTable object named zTab.
|
||||
*/
|
||||
static int cbFindTable(
|
||||
sqlite3_changebatch *p,
|
||||
const char *zTab,
|
||||
BatchTable **ppTab
|
||||
){
|
||||
BatchTable *pRet = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
for(pRet=p->pTab; pRet; pRet=pRet->pNext){
|
||||
if( 0==sqlite3_stricmp(zTab, pRet->zTab) ) break;
|
||||
}
|
||||
|
||||
if( pRet==0 ){
|
||||
int nTab = strlen(zTab);
|
||||
pRet = (BatchTable*)cbMalloc(&rc, nTab + sizeof(BatchTable));
|
||||
if( pRet ){
|
||||
sqlite3_stmt *pIndexList = 0;
|
||||
char *zIndexList = 0;
|
||||
int rc2;
|
||||
memcpy(pRet->zTab, zTab, nTab);
|
||||
|
||||
zIndexList = sqlite3_mprintf("PRAGMA main.index_list = %Q", zTab);
|
||||
if( zIndexList==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = sqlite3_prepare_v2(p->db, zIndexList, -1, &pIndexList, 0);
|
||||
sqlite3_free(zIndexList);
|
||||
}
|
||||
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pIndexList) ){
|
||||
if( sqlite3_column_int(pIndexList, 2) ){
|
||||
const char *zIdx = (const char*)sqlite3_column_text(pIndexList, 1);
|
||||
const char *zTyp = (const char*)sqlite3_column_text(pIndexList, 3);
|
||||
rc = cbAddIndex(p, pRet, zIdx, (zTyp[0]=='p'));
|
||||
}
|
||||
}
|
||||
rc2 = sqlite3_finalize(pIndexList);
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
pRet->pNext = p->pTab;
|
||||
p->pTab = pRet;
|
||||
}else{
|
||||
cbFreeTable(pRet);
|
||||
pRet = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*ppTab = pRet;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Extract value iVal from the changeset iterator passed as the first
|
||||
** argument. Set *ppVal to point to the value before returning.
|
||||
**
|
||||
** This function attempts to extract the value using function xVal
|
||||
** (which is always either sqlite3changeset_new or sqlite3changeset_old).
|
||||
** If the call returns SQLITE_OK but does not supply an sqlite3_value*
|
||||
** pointer, an attempt to extract the value is made using the xFallback
|
||||
** function.
|
||||
*/
|
||||
static int cbGetChangesetValue(
|
||||
sqlite3_changeset_iter *pIter,
|
||||
int (*xVal)(sqlite3_changeset_iter*,int,sqlite3_value**),
|
||||
int (*xFallback)(sqlite3_changeset_iter*,int,sqlite3_value**),
|
||||
int iVal,
|
||||
sqlite3_value **ppVal
|
||||
){
|
||||
int rc = xVal(pIter, iVal, ppVal);
|
||||
if( rc==SQLITE_OK && *ppVal==0 && xFallback ){
|
||||
rc = xFallback(pIter, iVal, ppVal);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int cbAddToHash(
|
||||
sqlite3_changebatch *p,
|
||||
sqlite3_changeset_iter *pIter,
|
||||
BatchIndex *pIdx,
|
||||
int (*xVal)(sqlite3_changeset_iter*,int,sqlite3_value**),
|
||||
int (*xFallback)(sqlite3_changeset_iter*,int,sqlite3_value**),
|
||||
int *pbConf
|
||||
){
|
||||
BatchIndexEntry *pNew;
|
||||
int sz = pIdx->nCol;
|
||||
int i;
|
||||
int iOut = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
for(i=0; rc==SQLITE_OK && i<pIdx->nCol; i++){
|
||||
sqlite3_value *pVal;
|
||||
rc = cbGetChangesetValue(pIter, xVal, xFallback, pIdx->aiCol[i], &pVal);
|
||||
if( rc==SQLITE_OK ){
|
||||
int eType = 0;
|
||||
if( pVal ) eType = sqlite3_value_type(pVal);
|
||||
switch( eType ){
|
||||
case 0:
|
||||
case SQLITE_NULL:
|
||||
return SQLITE_OK;
|
||||
|
||||
case SQLITE_INTEGER:
|
||||
sz += 8;
|
||||
break;
|
||||
case SQLITE_FLOAT:
|
||||
sz += 8;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
|
||||
sz += sqlite3_value_bytes(pVal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pNew = cbMalloc(&rc, sizeof(BatchIndexEntry) + sz);
|
||||
if( pNew ){
|
||||
pNew->iChangesetId = p->iChangesetId;
|
||||
pNew->iIdxId = pIdx->iId;
|
||||
pNew->szRecord = sz;
|
||||
|
||||
for(i=0; i<pIdx->nCol; i++){
|
||||
int eType;
|
||||
sqlite3_value *pVal;
|
||||
rc = cbGetChangesetValue(pIter, xVal, xFallback, pIdx->aiCol[i], &pVal);
|
||||
if( rc!=SQLITE_OK ) break; /* coverage: condition is never true */
|
||||
eType = sqlite3_value_type(pVal);
|
||||
pNew->aRecord[iOut++] = eType;
|
||||
switch( eType ){
|
||||
case SQLITE_INTEGER: {
|
||||
sqlite3_int64 i64 = sqlite3_value_int64(pVal);
|
||||
memcpy(&pNew->aRecord[iOut], &i64, 8);
|
||||
iOut += 8;
|
||||
break;
|
||||
}
|
||||
case SQLITE_FLOAT: {
|
||||
double d64 = sqlite3_value_double(pVal);
|
||||
memcpy(&pNew->aRecord[iOut], &d64, sizeof(double));
|
||||
iOut += sizeof(double);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
int nByte = sqlite3_value_bytes(pVal);
|
||||
const char *z = (const char*)sqlite3_value_blob(pVal);
|
||||
memcpy(&pNew->aRecord[iOut], z, nByte);
|
||||
iOut += nByte;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && p->nEntry>=(p->nHash/2) ){
|
||||
rc = cbHashResize(p);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
BatchIndexEntry *pIter;
|
||||
int iHash = cbHash(p, pNew);
|
||||
|
||||
assert( iHash>=0 && iHash<p->nHash );
|
||||
for(pIter=p->apHash[iHash]; pIter; pIter=pIter->pNext){
|
||||
if( pNew->szRecord==pIter->szRecord
|
||||
&& 0==memcmp(pNew->aRecord, pIter->aRecord, pNew->szRecord)
|
||||
){
|
||||
if( pNew->iChangesetId!=pIter->iChangesetId ){
|
||||
*pbConf = 1;
|
||||
}
|
||||
cbFree(pNew);
|
||||
pNew = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( pNew ){
|
||||
pNew->pNext = p->apHash[iHash];
|
||||
p->apHash[iHash] = pNew;
|
||||
p->nEntry++;
|
||||
}
|
||||
}else{
|
||||
cbFree(pNew);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Add a changeset to the current batch.
|
||||
*/
|
||||
int sqlite3changebatch_add(sqlite3_changebatch *p, void *pBuf, int nBuf){
|
||||
sqlite3_changeset_iter *pIter; /* Iterator opened on pBuf/nBuf */
|
||||
int rc; /* Return code */
|
||||
int bConf = 0; /* Conflict was detected */
|
||||
|
||||
rc = sqlite3changeset_start(&pIter, nBuf, pBuf);
|
||||
if( rc==SQLITE_OK ){
|
||||
int rc2;
|
||||
for(rc2 = sqlite3changeset_next(pIter);
|
||||
rc2==SQLITE_ROW;
|
||||
rc2 = sqlite3changeset_next(pIter)
|
||||
){
|
||||
BatchTable *pTab;
|
||||
BatchIndex *pIdx;
|
||||
const char *zTab; /* Table this change applies to */
|
||||
int nCol; /* Number of columns in table */
|
||||
int op; /* UPDATE, INSERT or DELETE */
|
||||
|
||||
sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
|
||||
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
|
||||
|
||||
rc = cbFindTable(p, zTab, &pTab);
|
||||
assert( pTab || rc!=SQLITE_OK );
|
||||
if( pTab ){
|
||||
for(pIdx=pTab->pIdx; pIdx && rc==SQLITE_OK; pIdx=pIdx->pNext){
|
||||
if( op==SQLITE_UPDATE && pIdx->bPk ) continue;
|
||||
if( op==SQLITE_UPDATE || op==SQLITE_DELETE ){
|
||||
rc = cbAddToHash(p, pIter, pIdx, sqlite3changeset_old, 0, &bConf);
|
||||
}
|
||||
if( op==SQLITE_UPDATE || op==SQLITE_INSERT ){
|
||||
rc = cbAddToHash(p, pIter, pIdx,
|
||||
sqlite3changeset_new, sqlite3changeset_old, &bConf
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
}
|
||||
|
||||
rc2 = sqlite3changeset_finalize(pIter);
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && bConf ){
|
||||
rc = SQLITE_CONSTRAINT;
|
||||
}
|
||||
p->iChangesetId++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Zero an existing changebatch object.
|
||||
*/
|
||||
void sqlite3changebatch_zero(sqlite3_changebatch *p){
|
||||
int i;
|
||||
for(i=0; i<p->nHash; i++){
|
||||
BatchIndexEntry *pEntry;
|
||||
BatchIndexEntry *pNext;
|
||||
for(pEntry=p->apHash[i]; pEntry; pEntry=pNext){
|
||||
pNext = pEntry->pNext;
|
||||
cbFree(pEntry);
|
||||
}
|
||||
}
|
||||
cbFree(p->apHash);
|
||||
p->nHash = 0;
|
||||
p->apHash = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Delete a changebatch object.
|
||||
*/
|
||||
void sqlite3changebatch_delete(sqlite3_changebatch *p){
|
||||
BatchTable *pTab;
|
||||
BatchTable *pTabNext;
|
||||
|
||||
sqlite3changebatch_zero(p);
|
||||
for(pTab=p->pTab; pTab; pTab=pTabNext){
|
||||
pTabNext = pTab->pNext;
|
||||
cbFreeTable(pTab);
|
||||
}
|
||||
cbFree(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the db handle.
|
||||
*/
|
||||
sqlite3 *sqlite3changebatch_db(sqlite3_changebatch *p){
|
||||
return p->db;
|
||||
}
|
||||
|
||||
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
@@ -0,0 +1,82 @@
|
||||
|
||||
#if !defined(SQLITECHANGEBATCH_H_)
|
||||
#define SQLITECHANGEBATCH_H_ 1
|
||||
|
||||
typedef struct sqlite3_changebatch sqlite3_changebatch;
|
||||
|
||||
/*
|
||||
** Create a new changebatch object for detecting conflicts between
|
||||
** changesets associated with a schema equivalent to that of the "main"
|
||||
** database of the open database handle db passed as the first
|
||||
** parameter. It is the responsibility of the caller to ensure that
|
||||
** the database handle is not closed until after the changebatch
|
||||
** object has been deleted.
|
||||
**
|
||||
** A changebatch object is used to detect batches of non-conflicting
|
||||
** changesets. Changesets that do not conflict may be applied to the
|
||||
** target database in any order without affecting the final state of
|
||||
** the database.
|
||||
**
|
||||
** The changebatch object only works reliably if PRIMARY KEY and UNIQUE
|
||||
** constraints on tables affected by the changesets use collation
|
||||
** sequences that are equivalent to built-in collation sequence
|
||||
** BINARY for the == operation.
|
||||
**
|
||||
** If successful, SQLITE_OK is returned and (*pp) set to point to
|
||||
** the new changebatch object. If an error occurs, an SQLite error
|
||||
** code is returned and the final value of (*pp) is undefined.
|
||||
*/
|
||||
int sqlite3changebatch_new(sqlite3 *db, sqlite3_changebatch **pp);
|
||||
|
||||
/*
|
||||
** Argument p points to a buffer containing a changeset n bytes in
|
||||
** size. Assuming no error occurs, this function returns SQLITE_OK
|
||||
** if the changeset does not conflict with any changeset passed
|
||||
** to an sqlite3changebatch_add() call made on the same
|
||||
** sqlite3_changebatch* handle since the most recent call to
|
||||
** sqlite3changebatch_zero(). If the changeset does conflict with
|
||||
** an earlier such changeset, SQLITE_CONSTRAINT is returned. Or,
|
||||
** if an error occurs, some other SQLite error code may be returned.
|
||||
**
|
||||
** One changeset is said to conflict with another if
|
||||
** either:
|
||||
**
|
||||
** * the two changesets contain operations (INSERT, UPDATE or
|
||||
** DELETE) on the same row, identified by primary key, or
|
||||
**
|
||||
** * the two changesets contain operations (INSERT, UPDATE or
|
||||
** DELETE) on rows with identical values in any combination
|
||||
** of fields constrained by a UNIQUE constraint.
|
||||
**
|
||||
** Even if this function returns SQLITE_CONFLICT, the current
|
||||
** changeset is added to the internal data structures - so future
|
||||
** calls to this function may conflict with it. If this function
|
||||
** returns any result code other than SQLITE_OK or SQLITE_CONFLICT,
|
||||
** the result of any future call to sqlite3changebatch_add() is
|
||||
** undefined.
|
||||
**
|
||||
** Only changesets may be passed to this function. Passing a
|
||||
** patchset to this function results in an SQLITE_MISUSE error.
|
||||
*/
|
||||
int sqlite3changebatch_add(sqlite3_changebatch*, void *p, int n);
|
||||
|
||||
/*
|
||||
** Zero a changebatch object. This causes the records of all earlier
|
||||
** calls to sqlite3changebatch_add() to be discarded.
|
||||
*/
|
||||
void sqlite3changebatch_zero(sqlite3_changebatch*);
|
||||
|
||||
/*
|
||||
** Return a copy of the first argument passed to the sqlite3changebatch_new()
|
||||
** call used to create the changebatch object passed as the only argument
|
||||
** to this function.
|
||||
*/
|
||||
sqlite3 *sqlite3changebatch_db(sqlite3_changebatch*);
|
||||
|
||||
/*
|
||||
** Delete a changebatch object.
|
||||
*/
|
||||
void sqlite3changebatch_delete(sqlite3_changebatch*);
|
||||
|
||||
#endif /* !defined(SQLITECHANGEBATCH_H_) */
|
||||
|
||||
@@ -25,6 +25,13 @@ typedef struct SessionInput SessionInput;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The three different types of changesets generated.
|
||||
*/
|
||||
#define SESSIONS_PATCHSET 0
|
||||
#define SESSIONS_CHANGESET 1
|
||||
#define SESSIONS_FULLCHANGESET 2
|
||||
|
||||
static int sessions_strm_chunk_size = SESSIONS_STRM_CHUNK_SIZE;
|
||||
|
||||
typedef struct SessionHook SessionHook;
|
||||
@@ -2052,7 +2059,7 @@ static void sessionAppendCol(
|
||||
*/
|
||||
static int sessionAppendUpdate(
|
||||
SessionBuffer *pBuf, /* Buffer to append to */
|
||||
int bPatchset, /* True for "patchset", 0 for "changeset" */
|
||||
int ePatchset, /* True for "patchset", 0 for "changeset" */
|
||||
sqlite3_stmt *pStmt, /* Statement handle pointing at new row */
|
||||
SessionChange *p, /* Object containing old values */
|
||||
u8 *abPK /* Boolean array - true for PK columns */
|
||||
@@ -2115,8 +2122,8 @@ static int sessionAppendUpdate(
|
||||
|
||||
/* Add a field to the old.* record. This is omitted if this modules is
|
||||
** currently generating a patchset. */
|
||||
if( bPatchset==0 ){
|
||||
if( bChanged || abPK[i] ){
|
||||
if( ePatchset!=SESSIONS_PATCHSET ){
|
||||
if( ePatchset==SESSIONS_FULLCHANGESET || bChanged || abPK[i] ){
|
||||
sessionAppendBlob(pBuf, pCsr, nAdvance, &rc);
|
||||
}else{
|
||||
sessionAppendByte(pBuf, 0, &rc);
|
||||
@@ -2125,7 +2132,7 @@ static int sessionAppendUpdate(
|
||||
|
||||
/* Add a field to the new.* record. Or the only record if currently
|
||||
** generating a patchset. */
|
||||
if( bChanged || (bPatchset && abPK[i]) ){
|
||||
if( bChanged || (ePatchset==SESSIONS_PATCHSET && abPK[i]) ){
|
||||
sessionAppendCol(&buf2, pStmt, i, &rc);
|
||||
}else{
|
||||
sessionAppendByte(&buf2, 0, &rc);
|
||||
@@ -2151,7 +2158,7 @@ static int sessionAppendUpdate(
|
||||
*/
|
||||
static int sessionAppendDelete(
|
||||
SessionBuffer *pBuf, /* Buffer to append to */
|
||||
int bPatchset, /* True for "patchset", 0 for "changeset" */
|
||||
int eChangeset, /* One of SESSIONS_CHANGESET etc. */
|
||||
SessionChange *p, /* Object containing old values */
|
||||
int nCol, /* Number of columns in table */
|
||||
u8 *abPK /* Boolean array - true for PK columns */
|
||||
@@ -2161,7 +2168,7 @@ static int sessionAppendDelete(
|
||||
sessionAppendByte(pBuf, SQLITE_DELETE, &rc);
|
||||
sessionAppendByte(pBuf, p->bIndirect, &rc);
|
||||
|
||||
if( bPatchset==0 ){
|
||||
if( eChangeset!=SESSIONS_PATCHSET ){
|
||||
sessionAppendBlob(pBuf, p->aRecord, p->nRecord, &rc);
|
||||
}else{
|
||||
int i;
|
||||
@@ -2334,12 +2341,12 @@ static int sessionSelectBind(
|
||||
*/
|
||||
static void sessionAppendTableHdr(
|
||||
SessionBuffer *pBuf, /* Append header to this buffer */
|
||||
int bPatchset, /* Use the patchset format if true */
|
||||
int ePatchset, /* Use the patchset format if true */
|
||||
SessionTable *pTab, /* Table object to append header for */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
/* Write a table header */
|
||||
sessionAppendByte(pBuf, (bPatchset ? 'P' : 'T'), pRc);
|
||||
sessionAppendByte(pBuf, (ePatchset==SESSIONS_PATCHSET) ? 'P' : 'T', pRc);
|
||||
sessionAppendVarint(pBuf, pTab->nCol, pRc);
|
||||
sessionAppendBlob(pBuf, pTab->abPK, pTab->nCol, pRc);
|
||||
sessionAppendBlob(pBuf, (u8 *)pTab->zName, (int)strlen(pTab->zName)+1, pRc);
|
||||
@@ -2357,7 +2364,7 @@ static void sessionAppendTableHdr(
|
||||
*/
|
||||
static int sessionGenerateChangeset(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
int bPatchset, /* True for patchset, false for changeset */
|
||||
int ePatchset, /* One of SESSIONS_CHANGESET etc. */
|
||||
int (*xOutput)(void *pOut, const void *pData, int nData),
|
||||
void *pOut, /* First argument for xOutput */
|
||||
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
|
||||
@@ -2402,7 +2409,7 @@ static int sessionGenerateChangeset(
|
||||
}
|
||||
|
||||
/* Write a table header */
|
||||
sessionAppendTableHdr(&buf, bPatchset, pTab, &rc);
|
||||
sessionAppendTableHdr(&buf, ePatchset, pTab, &rc);
|
||||
|
||||
/* Build and compile a statement to execute: */
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -2426,10 +2433,10 @@ static int sessionGenerateChangeset(
|
||||
sessionAppendCol(&buf, pSel, iCol, &rc);
|
||||
}
|
||||
}else{
|
||||
rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK);
|
||||
rc = sessionAppendUpdate(&buf, ePatchset, pSel, p, abPK);
|
||||
}
|
||||
}else if( p->op!=SQLITE_INSERT ){
|
||||
rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK);
|
||||
rc = sessionAppendDelete(&buf, ePatchset, p, nCol, abPK);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_reset(pSel);
|
||||
@@ -2486,7 +2493,8 @@ int sqlite3session_changeset(
|
||||
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
|
||||
void **ppChangeset /* OUT: Buffer containing changeset */
|
||||
){
|
||||
return sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset);
|
||||
return sessionGenerateChangeset(
|
||||
pSession, SESSIONS_CHANGESET, 0, 0, pnChangeset, ppChangeset);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2497,7 +2505,8 @@ int sqlite3session_changeset_strm(
|
||||
int (*xOutput)(void *pOut, const void *pData, int nData),
|
||||
void *pOut
|
||||
){
|
||||
return sessionGenerateChangeset(pSession, 0, xOutput, pOut, 0, 0);
|
||||
return sessionGenerateChangeset(
|
||||
pSession, SESSIONS_CHANGESET, xOutput, pOut, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2508,7 +2517,8 @@ int sqlite3session_patchset_strm(
|
||||
int (*xOutput)(void *pOut, const void *pData, int nData),
|
||||
void *pOut
|
||||
){
|
||||
return sessionGenerateChangeset(pSession, 1, xOutput, pOut, 0, 0);
|
||||
return sessionGenerateChangeset(
|
||||
pSession, SESSIONS_PATCHSET, xOutput, pOut, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2523,9 +2533,20 @@ int sqlite3session_patchset(
|
||||
int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */
|
||||
void **ppPatchset /* OUT: Buffer containing changeset */
|
||||
){
|
||||
return sessionGenerateChangeset(pSession, 1, 0, 0, pnPatchset, ppPatchset);
|
||||
return sessionGenerateChangeset(
|
||||
pSession, SESSIONS_PATCHSET, 0, 0, pnPatchset, ppPatchset);
|
||||
}
|
||||
|
||||
int sqlite3session_fullchangeset(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
|
||||
void **ppChangeset /* OUT: Buffer containing changeset */
|
||||
){
|
||||
return sessionGenerateChangeset(
|
||||
pSession, SESSIONS_FULLCHANGESET, 0, 0, pnChangeset, ppChangeset);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Enable or disable the session object passed as the first argument.
|
||||
*/
|
||||
@@ -4927,10 +4948,11 @@ static int sessionChangegroupOutput(
|
||||
** hash tables attached to the SessionTable objects in list p->pList.
|
||||
*/
|
||||
for(pTab=pGrp->pList; rc==SQLITE_OK && pTab; pTab=pTab->pNext){
|
||||
int eChangeset = pGrp->bPatch ? SESSIONS_PATCHSET : SESSIONS_CHANGESET;
|
||||
int i;
|
||||
if( pTab->nEntry==0 ) continue;
|
||||
|
||||
sessionAppendTableHdr(&buf, pGrp->bPatch, pTab, &rc);
|
||||
sessionAppendTableHdr(&buf, eChangeset, pTab, &rc);
|
||||
for(i=0; i<pTab->nChange; i++){
|
||||
SessionChange *p;
|
||||
for(p=pTab->apChange[i]; p; p=p->pNext){
|
||||
|
||||
@@ -323,6 +323,32 @@ int sqlite3session_changeset(
|
||||
void **ppChangeset /* OUT: Buffer containing changeset */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Generate A Full Changeset From A Session Object
|
||||
**
|
||||
** This function is similar to sqlite3session_changeset(), except that for
|
||||
** each row affected by an UPDATE statement, all old.* values are recorded
|
||||
** as part of the changeset, not just those modified.
|
||||
*/
|
||||
int sqlite3session_fullchangeset(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
|
||||
void **ppChangeset /* OUT: Buffer containing changeset */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Generate A Full Changeset From A Session Object
|
||||
**
|
||||
** This function is similar to sqlite3session_changeset(), except that for
|
||||
** each row affected by an UPDATE statement, all old.* values are recorded
|
||||
** as part of the changeset, not just those modified.
|
||||
*/
|
||||
int sqlite3session_fullchangeset(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
|
||||
void **ppChangeset /* OUT: Buffer containing changeset */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Load The Difference Between Tables Into A Session
|
||||
** METHOD: sqlite3_session
|
||||
|
||||
+141
-11
@@ -218,6 +218,7 @@ static int testStreamOutput(
|
||||
** $session indirect INTEGER
|
||||
** $session patchset
|
||||
** $session table_filter SCRIPT
|
||||
** $session fullchangeset
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_session_cmd(
|
||||
void *clientData,
|
||||
@@ -231,17 +232,17 @@ static int SQLITE_TCLAPI test_session_cmd(
|
||||
const char *zSub;
|
||||
int nArg;
|
||||
const char *zMsg;
|
||||
int iSub;
|
||||
} aSub[] = {
|
||||
{ "attach", 1, "TABLE", }, /* 0 */
|
||||
{ "changeset", 0, "", }, /* 1 */
|
||||
{ "delete", 0, "", }, /* 2 */
|
||||
{ "enable", 1, "BOOL", }, /* 3 */
|
||||
{ "indirect", 1, "BOOL", }, /* 4 */
|
||||
{ "isempty", 0, "", }, /* 5 */
|
||||
{ "table_filter", 1, "SCRIPT", }, /* 6 */
|
||||
{ "attach", 1, "TABLE" }, /* 0 */
|
||||
{ "changeset", 0, "" }, /* 1 */
|
||||
{ "delete", 0, "" }, /* 2 */
|
||||
{ "enable", 1, "BOOL" }, /* 3 */
|
||||
{ "indirect", 1, "BOOL" }, /* 4 */
|
||||
{ "isempty", 0, "" }, /* 5 */
|
||||
{ "table_filter", 1, "SCRIPT" }, /* 6 */
|
||||
{ "patchset", 0, "", }, /* 7 */
|
||||
{ "diff", 2, "FROMDB TBL", }, /* 8 */
|
||||
{ "diff", 2, "FROMDB TBL" }, /* 8 */
|
||||
{ "fullchangeset",0, "" }, /* 9 */
|
||||
{ 0 }
|
||||
};
|
||||
int iSub;
|
||||
@@ -271,10 +272,11 @@ static int SQLITE_TCLAPI test_session_cmd(
|
||||
break;
|
||||
}
|
||||
|
||||
case 9: /* fullchangeset */
|
||||
case 7: /* patchset */
|
||||
case 1: { /* changeset */
|
||||
TestSessionsBlob o = {0, 0};
|
||||
if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
|
||||
if( iSub!=9 && test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
|
||||
void *pCtx = (void*)&o;
|
||||
if( iSub==7 ){
|
||||
rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx);
|
||||
@@ -284,6 +286,8 @@ static int SQLITE_TCLAPI test_session_cmd(
|
||||
}else{
|
||||
if( iSub==7 ){
|
||||
rc = sqlite3session_patchset(pSession, &o.n, &o.p);
|
||||
}else if( iSub==9 ){
|
||||
rc = sqlite3session_fullchangeset(pSession, &o.n, &o.p);
|
||||
}else{
|
||||
rc = sqlite3session_changeset(pSession, &o.n, &o.p);
|
||||
}
|
||||
@@ -298,6 +302,7 @@ static int SQLITE_TCLAPI test_session_cmd(
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 2: /* delete */
|
||||
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
|
||||
break;
|
||||
@@ -478,7 +483,7 @@ static int test_filter_handler(
|
||||
|
||||
Tcl_DecrRefCount(pEval);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
static int test_conflict_handler(
|
||||
void *pCtx, /* Pointer to TestConflictHandler structure */
|
||||
@@ -1120,6 +1125,127 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
#include "sqlite3changebatch.h"
|
||||
|
||||
typedef struct TestChangebatch TestChangebatch;
|
||||
struct TestChangebatch {
|
||||
sqlite3_changebatch *pChangebatch;
|
||||
};
|
||||
|
||||
/*
|
||||
** Tclcmd: $changebatch add BLOB
|
||||
** $changebatch zero
|
||||
** $changebatch delete
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_changebatch_cmd(
|
||||
void *clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
TestChangebatch *p = (TestChangebatch*)clientData;
|
||||
sqlite3_changebatch *pChangebatch = p->pChangebatch;
|
||||
struct SessionSubcmd {
|
||||
const char *zSub;
|
||||
int nArg;
|
||||
const char *zMsg;
|
||||
int iSub;
|
||||
} aSub[] = {
|
||||
{ "add", 1, "CHANGESET", }, /* 0 */
|
||||
{ "zero", 0, "", }, /* 1 */
|
||||
{ "delete", 0, "", }, /* 2 */
|
||||
{ 0 }
|
||||
};
|
||||
int iSub;
|
||||
int rc;
|
||||
|
||||
if( objc<2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
rc = Tcl_GetIndexFromObjStruct(interp,
|
||||
objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
|
||||
);
|
||||
if( rc!=TCL_OK ) return rc;
|
||||
if( objc!=2+aSub[iSub].nArg ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
switch( iSub ){
|
||||
case 0: { /* add */
|
||||
int nArg;
|
||||
unsigned char *pArg = Tcl_GetByteArrayFromObj(objv[2], &nArg);
|
||||
rc = sqlite3changebatch_add(pChangebatch, pArg, nArg);
|
||||
if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){
|
||||
return test_session_error(interp, rc, 0);
|
||||
}else{
|
||||
extern const char *sqlite3ErrName(int);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: { /* zero */
|
||||
sqlite3changebatch_zero(pChangebatch);
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: /* delete */
|
||||
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
|
||||
break;
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static void SQLITE_TCLAPI test_changebatch_del(void *clientData){
|
||||
TestChangebatch *p = (TestChangebatch*)clientData;
|
||||
sqlite3changebatch_delete(p->pChangebatch);
|
||||
ckfree((char*)p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: sqlite3changebatch CMD DB-HANDLE
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_sqlite3changebatch(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3 *db;
|
||||
Tcl_CmdInfo info;
|
||||
int rc; /* sqlite3session_create() return code */
|
||||
TestChangebatch *p; /* New wrapper object */
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
|
||||
Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
db = *(sqlite3 **)info.objClientData;
|
||||
|
||||
p = (TestChangebatch*)ckalloc(sizeof(TestChangebatch));
|
||||
memset(p, 0, sizeof(TestChangebatch));
|
||||
rc = sqlite3changebatch_new(db, &p->pChangebatch);
|
||||
if( rc!=SQLITE_OK ){
|
||||
ckfree((char*)p);
|
||||
return test_session_error(interp, rc, 0);
|
||||
}
|
||||
|
||||
Tcl_CreateObjCommand(
|
||||
interp, Tcl_GetString(objv[1]), test_changebatch_cmd, (ClientData)p,
|
||||
test_changebatch_del
|
||||
);
|
||||
Tcl_SetObjResult(interp, objv[1]);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: CMD configure REBASE-BLOB
|
||||
** tclcmd: CMD rebase CHANGESET
|
||||
@@ -1302,6 +1428,10 @@ int TestSession_Init(Tcl_Interp *interp){
|
||||
Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
Tcl_CreateObjCommand(
|
||||
interp, "sqlite3changebatch", test_sqlite3changebatch, 0, 0
|
||||
);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -357,6 +357,7 @@ TESTSRC = \
|
||||
#
|
||||
TESTSRC += \
|
||||
$(TOP)/ext/misc/amatch.c \
|
||||
$(TOP)/ext/misc/bgckpt.c \
|
||||
$(TOP)/ext/misc/carray.c \
|
||||
$(TOP)/ext/misc/closure.c \
|
||||
$(TOP)/ext/misc/csv.c \
|
||||
@@ -432,7 +433,8 @@ TESTSRC2 = \
|
||||
$(TOP)/ext/async/sqlite3async.c \
|
||||
$(TOP)/ext/misc/stmt.c \
|
||||
$(TOP)/ext/session/sqlite3session.c \
|
||||
$(TOP)/ext/session/test_session.c
|
||||
$(TOP)/ext/session/sqlite3changebatch.c \
|
||||
$(TOP)/ext/session/test_session.c
|
||||
|
||||
# Header files used by all library source files.
|
||||
#
|
||||
@@ -966,6 +968,9 @@ THREADTEST3_SRC = $(TOP)/test/threadtest3.c \
|
||||
threadtest3$(EXE): sqlite3.o $(THREADTEST3_SRC) $(TOP)/src/test_multiplex.c
|
||||
$(TCCX) $(TOP)/test/threadtest3.c $(TOP)/src/test_multiplex.c sqlite3.o -o $@ $(THREADLIB)
|
||||
|
||||
bc_test1$(EXE): sqlite3.o $(TOP)/test/bc_test1.c $(TOP)/test/tt3_core.c
|
||||
$(TCCX) $(TOP)/test/bc_test1.c sqlite3.o -o $@ $(THREADLIB)
|
||||
|
||||
threadtest: threadtest3$(EXE)
|
||||
./threadtest3$(EXE)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
C Add\sanother\stest\scase\sto\svacuum-into.test.
|
||||
D 2019-01-02T16:01:59.068
|
||||
C Merge\sdocumentation\schanges\sfrom\sbranch\s"begin-concurrent"\sinto\sthis\sbranch.
|
||||
D 2019-01-11T15:22:27.559
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F Makefile.in d8b254f8bb81bab43c340d70d17dc3babab40fcc8a348c8255881f780a45fee6
|
||||
@@ -37,9 +37,11 @@ F configure e2b4021dcb8ee6f9a91e7f4011316957868e8065019ac994f68e27363120c272 x
|
||||
F configure.ac 3552d3aecade98a9d4b64bceb48ffb7726cbc85902efde956812942f060fbd0a
|
||||
F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
|
||||
F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd
|
||||
F doc/begin_concurrent.md 4bee2c3990d1eb800f1ce3726a911292a8e4b889300b2ffd4b08d357370db299
|
||||
F doc/lemon.html 24956ab2995e55fe171e55bdd04f22b553957dc8bb43501dbb9311e30187e0d3
|
||||
F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710
|
||||
F doc/vfs-shm.txt e101f27ea02a8387ce46a05be2b1a902a021d37a
|
||||
F doc/wal2.md a807405a05e19a4945c5905a9ffa0fe45b8560dd7572461192501f565c19cdb5
|
||||
F ext/README.md fd5f78013b0a2bc6f0067afb19e6ad040e89a10179b4f6f03eee58fac5f169bd
|
||||
F ext/async/README.txt e12275968f6fde133a80e04387d0e839b0c51f91
|
||||
F ext/async/sqlite3async.c 0f3070cc3f5ede78f2b9361fb3b629ce200d7d74
|
||||
@@ -275,6 +277,7 @@ F ext/misc/README.md d6dd0fe1d8af77040216798a6a2b0c46c73054d2f0ea544fbbcdccf6f23
|
||||
F ext/misc/amatch.c c0a6a807a553eaa220bf69fca0353cd1587a3bfb3d2224fa425e3e6efcacc98a
|
||||
F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb
|
||||
F ext/misc/appendvfs.c 3777f22ec1057dc4e5fd89f2fbddcc7a29fbeef1ad038c736c54411bb1967af7
|
||||
F ext/misc/bgckpt.c 49ae19aa03e6da393db5d17da256374d4c4d36889fdd89d6e4bc93aca2b752e6
|
||||
F ext/misc/btreeinfo.c 4f0ebf278f46e68e6306c667917766cebc5550fd35d5de17847988e22892d4d2
|
||||
F ext/misc/carray.c ed96c218ea940b85c9a274c4d9c59fe9491c299147a38a8bba537687bd6c6005
|
||||
F ext/misc/closure.c 9f8fa11aa6c6e2f6d7296ffa88f103df4b46abd9602bcab3ea2f8fc24f334f63
|
||||
@@ -394,6 +397,8 @@ F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
|
||||
F ext/rtree/util/randomshape.tcl 54ee03d0d4a1c621806f7f44d5b78d2db8fac26e0e8687c36c4bd0203b27dbff
|
||||
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
|
||||
F ext/rtree/visual01.txt e9c2564083bcd30ec51b07f881bffbf0e12b50a3f6fced0c222c5c1d2f94ac66
|
||||
F ext/session/changebatch1.test 9ceaa8f7b2a505933e250fbe6cbc550e4ce1e59d
|
||||
F ext/session/changebatchfault.test be49c793219bf387ad692a60856b921f0854ad6d
|
||||
F ext/session/changeset.c 4ccbaa4531944c24584bf6a61ba3a39c62b6267a
|
||||
F ext/session/changesetfuzz.c 255826ee367781ec4c29a36c7c63e9ee6387dc03a8346cf240feb8d597958b0e
|
||||
F ext/session/changesetfuzz1.test 2e1b90d888fbf0eea5e1bd2f1e527a48cc85f8e0ff75df1ec4e320b21f580b3a
|
||||
@@ -423,16 +428,18 @@ F ext/session/sessioninvert.test ae1a003a9ab1f8d64227dbb5c3a4c97e65b561b01e7b295
|
||||
F ext/session/sessionrebase.test ccfa716b23bd1d3b03217ee58cfd90c78d4b99f53e6a9a2f05e82363b9142810
|
||||
F ext/session/sessionstat1.test 218d351cf9fcd6648f125a26b607b140310160184723c2666091b54450a68fb5
|
||||
F ext/session/sessionwor.test 07f0b304dc4df5454906069140bf6ec67edcaa3c548f3683354003cf2c22b64a
|
||||
F ext/session/sqlite3session.c f88c4bd0404da56246b08f5d584462f8979ba6aedf8c30762b553f7ae91f51cf
|
||||
F ext/session/sqlite3session.h 54d6356f5769d3695e5f63d719c6ee27671b2614973a2b675a3ff4d30d574233
|
||||
F ext/session/test_session.c 98797aba475a799376c9a42214f2d1debf2d0c3cb657d9c8bbf4f70bf3fb4aec
|
||||
F ext/session/sqlite3changebatch.c d5553b79e012ee2cb06c0a96bdf9dfe19e66354390ea0036cc46c4953142d517
|
||||
F ext/session/sqlite3changebatch.h e72016998c9a22d439ddfd547b69e1ebac810c24
|
||||
F ext/session/sqlite3session.c 994b1b691f3e1ab72a9d3949c2ca7dca4db3d9dd5ece5e34f74953411b8d36f9
|
||||
F ext/session/sqlite3session.h ef72e7738edcaf8d1a29e74480cac3b3171781870d248160b2d9147b8dac0d6b
|
||||
F ext/session/test_session.c 60e15d5db8ae7a0f521e70a7504ba1f74fc50548a25a5397808f487bc6a92b5d
|
||||
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
|
||||
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
|
||||
F ext/userauth/userauth.c f81aa5a3ecacf406f170c62a144405858f6f6de51dbdc0920134e629edbe2648
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
|
||||
F main.mk cca1ecdd5dfe4579ded84e5abdd38e1866bbb8b8cf58d1a24496e9b1b65580d7
|
||||
F main.mk a4b21dfef7c928f1036633e8983d421df9b9443f8116eb9c1daac1110f97e814
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
|
||||
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
|
||||
@@ -449,12 +456,12 @@ F src/analyze.c 58db66344a5c58dcabb57f26696f6f2993956c830446da40b444051d2fdaf644
|
||||
F src/attach.c 92b51739a885da8bd84bc9a05485f1e48148bce5c15432f059b45af98fff75cd
|
||||
F src/auth.c 0fac71038875693a937e506bceb492c5f136dd7b1249fbd4ae70b4e8da14f9df
|
||||
F src/backup.c 78d3cecfbe28230a3a9a1793e2ead609f469be43e8f486ca996006be551857ab
|
||||
F src/bitvec.c 17ea48eff8ba979f1f5b04cc484c7bb2be632f33
|
||||
F src/bitvec.c 8433d9e98dd6f2ea3286e0d2fe5d65de1bfc18a706486eb2026b01be066b5806
|
||||
F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6
|
||||
F src/btree.c 11c18d6d4b23570602d6d99db681d5c431917cab9f4321d87c7c31cd583f2964
|
||||
F src/btree.h febb2e817be499570b7a2e32a9bbb4b607a9234f6b84bb9ae84916d4806e96f2
|
||||
F src/btreeInt.h 620ab4c7235f43572cf3ac2ac8723cbdf68073be4d29da24897c7b77dda5fd96
|
||||
F src/build.c 5ca38181e826bcc35e0176c31a691d897ed0cc708fc935fd51e8023705cdcf1a
|
||||
F src/btree.c 33cceb8e62798dda3e2ac64411018283d237037d1c8a86c0c12589727bfb6f1a
|
||||
F src/btree.h 1ed41c71481a1196a520064f2282bc13d768bbd8ae2850e319a3048f8ee7cb3d
|
||||
F src/btreeInt.h 6c65e6c96f561596f6870c79a64d4706af81613881d7947e3f063e923f14115f
|
||||
F src/build.c 1c36fd26eba15fa4a81cd04d0008d87828a2da378c3f136306863a49b3663da3
|
||||
F src/callback.c 25dda5e1c2334a367b94a64077b1d06b2553369f616261ca6783c48bcb6bda73
|
||||
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
|
||||
F src/ctime.c 109e58d00f62e8e71ee1eb5944ac18b90171c928ab2e082e058056e1137cc20b
|
||||
@@ -474,7 +481,7 @@ F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
|
||||
F src/insert.c 1e0001758cd174177886cdfe23d69c6d6775f1c4e7fe946a041f1ef56ae85bc5
|
||||
F src/legacy.c 134ab3e3fae00a0f67a5187981d6935b24b337bcf0f4b3e5c9fa5763da95bf4e
|
||||
F src/loadext.c e6f10875d52aca3b7e57ce1ec174aeafc9b6c00b43000cd30d791f9cb490b7a6
|
||||
F src/main.c 008f45dd39cdc7fb9e7e1f61d75a5c97397f8e07427a8879be23e27747d5f948
|
||||
F src/main.c c96ec2fffaf1c3963b31be1d378defd205228fa628522f49a6dd48d99d901851
|
||||
F src/malloc.c 07295435093ce354c6d9063ac05a2eeae28bd251d2e63c48b3d67c12c76f7e18
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de
|
||||
@@ -494,16 +501,16 @@ F src/os.c 8aeb0b0f40f8f5b0da03fe49706695adaf42d2f516ab95abc72e86c245e119de
|
||||
F src/os.h 48388821692e87da174ea198bf96b1b2d9d83be5dfc908f673ee21fafbe0d432
|
||||
F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
|
||||
F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586
|
||||
F src/os_unix.c f6e91b8fd82af7afbfd073c4974ad6cdb8e62d9f65ceddb45167835a0567fdc0
|
||||
F src/os_unix.c 86eca42c3d955bebea0082450f978e5633448235f03f86b27a02538bb26e7fff
|
||||
F src/os_win.c 85d9e532d0444ab6c16d7431490c2e279e282aa0917b0e988996b1ae0de5c5a0
|
||||
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
|
||||
F src/pager.c 75e0f3cfa3962c714f519f8a3d1e67ecca1c91de0e010a036b988e40ce9e4c73
|
||||
F src/pager.h 217921e81eb5fe455caa5cda96061959706bcdd29ddb57166198645ef7822ac3
|
||||
F src/parse.y a3c0db595bc642c6ee1d72869842f7f5b0b6ebeb91c21d0a7cba631d27e7afbd
|
||||
F src/pager.c 24dbb25d447629bc8167dcf16dcb449a6eb2067393935d97ff42377798e44979
|
||||
F src/pager.h d8cf37b3415c742d1f267ae2e0e6495826a72d403cbdbefdab2e2f5ff2a1dde7
|
||||
F src/parse.y 8206217fe7fa96652aa1b8a797246e23b30a9d4c1a5175d9c12b13750f51dc2f
|
||||
F src/pcache.c 696a01f1a6370c1b50a09c15972bc3bee3333f8fcd1f2da8e9a76b1b062c59ee
|
||||
F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586
|
||||
F src/pcache1.c ddc9fc7d9861cf3a1f30660264b76b1ae9e1dce5dbba085cf001d5cb6b41cf8c
|
||||
F src/pragma.c 20811b404b6d302a1bedd4b8af8aab2794c5e87ba1f5cb6b42dbc3266ad9c603
|
||||
F src/pragma.c c1bfbee754da581ab1f1c299d0522ed1e52e265f1100cad14be546e6d055e3cd
|
||||
F src/pragma.h fdd03d78a7497f74a3f652909f945328480089189526841ae829ce7313d98d13
|
||||
F src/prepare.c d0918fb8d00b1ebf19655e7f3d28464b3fc3c43c16d36dd06092de02244a5180
|
||||
F src/printf.c 0f1177cf1dd4d7827bf64d840768514ec76409abecaca9e8b577dbd065150381
|
||||
@@ -512,15 +519,15 @@ F src/resolve.c 5f2f987aacba7548d10781f808e1b933f80abe0b6bc8e9922bc18b1d1faba339
|
||||
F src/rowset.c d977b011993aaea002cab3e0bb2ce50cf346000dff94e944d547b989f4b1fe93
|
||||
F src/select.c 4b0be1bfd2d8668e2711f877682361e06b412c53ce7a56303f1f6978ec33eca8
|
||||
F src/shell.c.in 207da30342db0b6fac8b2487abd60b059a5ea80cc9494bd1db76a1dd4aae7cca
|
||||
F src/sqlite.h.in b54cd42d2f3b739a00de540cafe2dcd0de3b8e1748a2db33a68def487e9e602f
|
||||
F src/sqlite.h.in a8ac449be3b5f7edeaf9066bec560323a61fcd032f199f7f462444afda119151
|
||||
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
||||
F src/sqlite3ext.h 960f1b86c3610fa23cb6a267572a97dcf286e77aa0dd3b9b23292ffaa1ea8683
|
||||
F src/sqliteInt.h a86007d427b9a7b97e9f1e1e0911eba3f70e1a3764a0a0aff0efd4d7bb029001
|
||||
F src/sqliteInt.h 3e0bcdea1623f50a6e886c1bbb6b37b9c4b132df6e4e6990479270423c259aa0
|
||||
F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b
|
||||
F src/status.c 46e7aec11f79dad50965a5ca5fa9de009f7d6bde08be2156f1538a0a296d4d0e
|
||||
F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
|
||||
F src/tclsqlite.c e72862a271348d779672b45a730c33fd0c535e630ff927e8ce4a0c908d1d28c6
|
||||
F src/test1.c f78dcd39d5cad63d33306bb963354f976007968bc95c45bc9fdfaeb2e1ec0eae
|
||||
F src/test1.c 457b9b838edecaceb536f626418de90b2f27fdccb36dad2673e5022a9c5e1c33
|
||||
F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
|
||||
F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
|
||||
F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6
|
||||
@@ -535,13 +542,13 @@ F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0
|
||||
F src/test_bestindex.c 78809f11026f18a93fcfd798d9479cba37e1201c830260bf1edc674b2fa9b857
|
||||
F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce
|
||||
F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274
|
||||
F src/test_config.c 5ebafbcd5c75ac1c16bb0c8fe926dc325cc03e780943a88ca50e0d9a4fc4d2f5
|
||||
F src/test_config.c b0bac20e47824b0959f3c1e77b6b2c72b32b2dc62cd8b78cba57b460682b0284
|
||||
F src/test_delete.c e2fe07646dff6300b48d49b2fee2fe192ed389e834dd635e3b3bac0ce0bf9f8f
|
||||
F src/test_demovfs.c a0c3bdd45ed044115c2c9f7779e56eafff18741e
|
||||
F src/test_devsym.c 1960abbb234b97e9b920f07e99503fc04b443f62bbc3c6ff2c2cea2133e3b8a2
|
||||
F src/test_fs.c 35a2f7dd8a915900873386331386d9ba1ae1b5026d74fd20c2807bc76221f291
|
||||
F src/test_func.c d12d805953bcb3bb19f71d29cdc93383b7b7a3369504d2b7e398a1bd77376294
|
||||
F src/test_hexio.c 1d4469ca61ab202a1fcec6543f584d2407205e8d
|
||||
F src/test_hexio.c 1a70ba996c8c8c028469b428ceca0d2c9c84fae68589984022ed3fc6e706eaf7
|
||||
F src/test_init.c 4413c211a94b62157ca4c145b3f27c497f03c664
|
||||
F src/test_intarray.c 39b4181662a0f33a427748d87218e7578d913e683dc27eab7098bb41617cac71
|
||||
F src/test_intarray.h d57ae92f420cda25e22790dac474d60961bd0c500cbaa3338a05152d4a669ef7
|
||||
@@ -563,7 +570,7 @@ F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe
|
||||
F src/test_sqllog.c 11e6ce7575f489155c604ac4b439f2ac1d3d5aef
|
||||
F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e
|
||||
F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939
|
||||
F src/test_tclsh.c 06317648b0d85a85fd823f7973b55535c59a3156c1ef59394fe511f932cfa78d
|
||||
F src/test_tclsh.c c225ebf05d8905e204580198ffaa09847256ac58af04af632fde3fd760475d90
|
||||
F src/test_tclvar.c 33ff42149494a39c5fbb0df3d25d6fafb2f668888e41c0688d07273dcb268dfc
|
||||
F src/test_thread.c 911d15fb14e19c0c542bdc8aabf981c2f10a4858
|
||||
F src/test_vfs.c c6c6a58f66b26876c7b5769fb323a58b2c7120299b5084e7212c4116f902cbaa
|
||||
@@ -580,20 +587,20 @@ F src/update.c 4e630e47852e206d0b29ec63ea0402e0b7ba328a1c19dd645ae8ac7bd0a378cf
|
||||
F src/upsert.c 0dd81b40206841814d46942a7337786932475f085716042d0cb2fc7791bf8ca4
|
||||
F src/utf.c 810fbfebe12359f10bc2a011520a6e10879ab2a163bcb26c74768eab82ea62a5
|
||||
F src/util.c d9eb0a6c4aae1b00a7369eadd7ca0bbe946cb4c953b6751aa20d357c2f482157
|
||||
F src/vacuum.c a9f389f41556c0ec310bc9169dc9476603c30a0a913ad92bfbc75c86886967ca
|
||||
F src/vdbe.c 98c7a06d1911e683ee0649979911483ce97fabfd758782be53e537b1d4655664
|
||||
F src/vacuum.c b95f6ab52334f7a13499e45e4374690736726bc0aaa7ee50e26a83f6bf0187c6
|
||||
F src/vdbe.c 3a60858cc1bd42b27ca9f71c095a095a5a454790385add16f9de3d89b904de33
|
||||
F src/vdbe.h 712bca562eaed1c25506b9faf9680bdc75fc42e2f4a1cd518d883fa79c7a4237
|
||||
F src/vdbeInt.h a76d5eed62c76bcd8de7afd3147fac1bc40c5a870582664bcd7d071ef437c37f
|
||||
F src/vdbeapi.c 57a2d794a8833f269b878dbc24e955369bdb379af6c4e93ebc5ce1a20fa3daf4
|
||||
F src/vdbeaux.c b230f12642040c5ede19f092d9ad7510f75add84888e66a00108948dec574fd2
|
||||
F src/vdbeaux.c 272c03011ea1b37266170803ff086ee51870a8f1bf3813701519b1884e59b1e1
|
||||
F src/vdbeblob.c f5c70f973ea3a9e915d1693278a5f890dc78594300cf4d54e64f2b0917c94191
|
||||
F src/vdbemem.c 7b3305bc4a5139f4536ac9b5f61da0f915e49d2e3fdfa87dfdfa9d7aba8bc1e9
|
||||
F src/vdbesort.c 90aad5a92608f2dd771c96749beabdb562c9d881131a860a7a5bccf66dc3be7f
|
||||
F src/vdbetrace.c 79d6dbbc479267b255a7de8080eee6e729928a0ef93ed9b0bfa5618875b48392
|
||||
F src/vtab.c 70188a745dc4e57d26e942681ff4b2912b7c8249ad5de3f60f0677b4337bcfaa
|
||||
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
|
||||
F src/wal.c 3f4f653daf234fe713edbcbca3fec2350417d159d28801feabc702a22c4e213f
|
||||
F src/wal.h 606292549f5a7be50b6227bd685fa76e3a4affad71bb8ac5ce4cb5c79f6a176a
|
||||
F src/wal.c d1908ffa514275dc6f27c4b27abc6d5a2ce18c89e98dce10925972266b960f37
|
||||
F src/wal.h c1a0f32c170634cf42908f8e978fa6bd8921f7364301fa34606cd171fce883b1
|
||||
F src/walker.c fb94aadc9099ff9c6506d0a8b88d51266005bcaa265403f3d7caf732a562eb66
|
||||
F src/where.c 981412c27abb9378d3024eae6f3040abd3c16db722f5ef8a7d613141ea2aea78
|
||||
F src/whereInt.h 5f14db426ca46a83eabab1ae9aa6d4b8f27504ad35b64c290916289b1ddb2e88
|
||||
@@ -672,6 +679,7 @@ F test/backup_malloc.test 0c9abdf74c51e7bedb66d504cd684f28d4bd4027
|
||||
F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f
|
||||
F test/badutf2.test f5bc7f2d280670ecd79b9cf4f0f1760c607fe51f
|
||||
F test/bc_common.tcl b5e42d80305be95697e6370e015af571e5333a1c
|
||||
F test/bc_test1.c e0a092579552e066ed4ce7bcdaecfa69c4aacc8d
|
||||
F test/bestindex1.test 852170bddbb21daa121fabcc274640ff83d7d8705912e8b5fe7ed2c5a9a9224a
|
||||
F test/bestindex2.test 9a0ccd320b6525eec3a706aae6cdab7e1b7b5abca75027e39f39f755e76e5928
|
||||
F test/bestindex3.test 7622e792ff2da16d262d3cea6ad914591ac4806d57ed128e6c940b7920b47b84
|
||||
@@ -728,6 +736,15 @@ F test/collateA.test b8218ab90d1fa5c59dcf156efabb1b2599c580d6
|
||||
F test/collateB.test 1e68906951b846570f29f20102ed91d29e634854ee47454d725f2151ecac0b95
|
||||
F test/colmeta.test 2c765ea61ee37bc43bbe6d6047f89004e6508eb1
|
||||
F test/colname.test fb28b3687e03625425bc216edf8b186ce974aa71008e2aa1f426a7dcb75a601d
|
||||
F test/concfault.test 500f17c3fcfe7705114422bcc6ddd3c740001a43
|
||||
F test/concfault2.test 34b3fd258836aa305475d00e804c7450ade92f0de0bf9fa620e701446669bb12
|
||||
F test/concurrent.test 86661967a680670127a62a819e60dc93c2d3d49043ac95b26dfa70d3e60dbde5
|
||||
F test/concurrent2.test f447dfae67e4d80a2b61e2796c2c081564a8126cf96c39a14780e89c8c53eff5
|
||||
F test/concurrent3.test 530671ac706f6a1d0f4992dbdd33a86408330d03cd90fb9e82ecb1b27f5fd081
|
||||
F test/concurrent4.test e0b12cd467137e50259df3b4f837507e82aaa07c35941c88664dc8ed1d089c44
|
||||
F test/concurrent5.test 0c16cbf7446af162a14e6def30445e94016064eb994e5aa4ebb2bebc59554176
|
||||
F test/concurrent6.test a7860e9ca13bb5fb76bcf41c5524fbfa9c37e6e258ecf84ffb5748a272488c67
|
||||
F test/concurrent7.test b96fa5c4cfdf8d5c0bc66b6934214500bad0260884a736f054ccc76e81aae85d
|
||||
F test/conflict.test c7cc007e2af151516ddf38f7412fe10d473a694f55e3df437e2c7b31c2590e8d
|
||||
F test/conflict2.test bb0b94cf7196c64a3cbd815c66d3ee98c2fecd9c
|
||||
F test/conflict3.test a83db76a6c3503b2fa057c7bfb08c318d8a422202d8bc5b86226e078e5b49ff9
|
||||
@@ -741,7 +758,7 @@ F test/corrupt6.test fc6a891716139665dae0073b6945e3670bf92568
|
||||
F test/corrupt7.test b036f94bda4b0b23a2919bf717046ce9ecca4543
|
||||
F test/corrupt8.test 2399dfe40d2c0c63af86706e30f3e6302a8d0516
|
||||
F test/corrupt9.test 730a3db08d4ab9aa43392ea30d9c2b4879cbff85
|
||||
F test/corruptA.test 112f4b2ae0b95ebf3ea63718642fb969a93acea557ace3a307234d19c245989b
|
||||
F test/corruptA.test 56e8f321adaf3411960e9d2c7136669d8e1a91cbde6cf401ea84e6d6c7ccbe10
|
||||
F test/corruptB.test 73a8d6c0b9833697ecf16b63e3c5c05c945b5dec
|
||||
F test/corruptC.test 138ecb02188ed1a719b533d4a139568204039f72f00e07a8d30d920bd83122db
|
||||
F test/corruptD.test b3c205fac7952b1de645ce44bb02335cd9e3e040
|
||||
@@ -1183,11 +1200,10 @@ F test/pagerfault2.test caf4c7facb914fd3b03a17b31ae2b180c8d6ca1f
|
||||
F test/pagerfault3.test 1003fcda009bf48a8e22a516e193b6ef0dd1bbd8
|
||||
F test/pageropt.test 84e4cc5cbca285357f7906e99b21be4f2bf5abc0
|
||||
F test/pagesize.test 5769fc62d8c890a83a503f67d47508dfdc543305
|
||||
F test/parser1.test 6ccdf5e459a5dc4673d3273dc311a7e9742ca952dd0551a6a6320d27035ce4b3
|
||||
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
|
||||
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
|
||||
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
|
||||
F test/permutations.test 61c0f61d9b26ebb4643b2ed848e9b3f55492f74e0addab66c7dcd70b081d7b03
|
||||
F test/permutations.test 7a4a5b031cff772029df1ab1a65e93d8bd453fbd721180e9d074c9219c32c175
|
||||
F test/pg_common.tcl 301ac19c1a52fd55166d26db929b3b89165c634d52b5f8ad76ea8cb06960db30
|
||||
F test/pragma.test c267bf02742c823a191960895b3d52933cebd7beee26757d1ed694f213fcd867
|
||||
F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f
|
||||
@@ -1209,7 +1225,7 @@ F test/quote.test 215897dbe8de1a6f701265836d6601cc6ed103e6
|
||||
F test/randexpr1.tcl 40dec52119ed3a2b8b2a773bce24b63a3a746459
|
||||
F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df
|
||||
F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736
|
||||
F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8
|
||||
F test/rdonly.test 21e99ee237265d0cf95a0c84b50c784e834acaa4ef05d92a27b262626a656682
|
||||
F test/regexp1.test 497ea812f264d12b6198d6e50a76be4a1973a9d8
|
||||
F test/regexp2.test 40e894223b3d6672655481493f1be12012f2b33c
|
||||
F test/reindex.test 44edd3966b474468b823d481eafef0c305022254
|
||||
@@ -1234,11 +1250,11 @@ F test/rowvalue9.test d8dd2c6ecac432dadaa79e41dc2434f007be1b6b
|
||||
F test/rowvaluefault.test 7cd9ccc6c2fbdd881672984087aad0491bb75504
|
||||
F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798
|
||||
F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09
|
||||
F test/savepoint.test 1f8a6b1aea9a0d05837adc463d4bf47bd9d0f1c842f1c2a9caccd639baf34bf9
|
||||
F test/savepoint.test ea1a6c08454e1a4edd973589bcf1fca83928ed09c48858fde1e8653b6daab278
|
||||
F test/savepoint2.test 9b8543940572a2f01a18298c3135ad0c9f4f67d7
|
||||
F test/savepoint4.test c8f8159ade6d2acd9128be61e1230f1c1edc6cc0
|
||||
F test/savepoint5.test 0735db177e0ebbaedc39812c8d065075d563c4fd
|
||||
F test/savepoint6.test f41279c5e137139fa5c21485773332c7adb98cd7
|
||||
F test/savepoint6.test 48a645a7bb3a59a6fcf06a7364cfe5b655c336760de39068f7c241b0fc80d963
|
||||
F test/savepoint7.test cde525ea3075283eb950cdcdefe23ead4f700daa
|
||||
F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2
|
||||
F test/scanstatus.test d14842d0a2757ee059bcffa365746453d60952ba1077980c9a348a9fefbd232a
|
||||
@@ -1364,7 +1380,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
|
||||
F test/temptable2.test d2940417496e2b9548e01d09990763fbe88c316504033256d51493e1f1a5ce6a
|
||||
F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
|
||||
F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
|
||||
F test/tester.tcl fa5656391e3b477508abe12b3b81f019b2e71397399ab38a2f32d8d7f3bf8e56
|
||||
F test/tester.tcl f93080be43af7d7687e5817c62b4097e06ea2ed0f1af3ba773675a9d2f90e971
|
||||
F test/thread001.test b61a29dd87cf669f5f6ac96124a7c97d71b0c80d9012746072055877055cf9ef
|
||||
F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58
|
||||
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
|
||||
@@ -1375,7 +1391,7 @@ F test/thread2.test f35d2106452b77523b3a2b7d1dcde2e5ee8f9e46
|
||||
F test/thread_common.tcl 334639cadcb9f912bf82aa73f49efd5282e6cadd
|
||||
F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b
|
||||
F test/threadtest2.c a70a8e94bef23339d34226eb9521015ef99f4df8
|
||||
F test/threadtest3.c 38a612ea62854349ed66372f330a40d73c5cf956
|
||||
F test/threadtest3.c 4b413718ab3ef5e4f9f1ab0502b89707c5a9ccdc
|
||||
F test/threadtest4.c c1e67136ceb6c7ec8184e56ac61db28f96bd2925
|
||||
F test/time-wordcount.sh 8e0b0f8109367827ad5d58f5cc849705731e4b90
|
||||
F test/tkt-02a8e81d44.test 6c80d9c7514e2a42d4918bf87bf6bc54f379110c
|
||||
@@ -1545,6 +1561,7 @@ F test/triggerE.test ede2e4bce4ba802337bd69d39447fa04a938e06d84a8bfc53c76850fc36
|
||||
F test/triggerF.test 5d76f0a8c428ff87a4d5ed52da06f6096a2c787a1e21b846111dfac4123de3ad
|
||||
F test/triggerG.test d5caeef6144ede2426dd13211fd72248241ff2ebc68e12a4c0bf30f5faa21499
|
||||
F test/tt3_checkpoint.c 9e75cf7c1c364f52e1c47fd0f14c4340a9db0fe1
|
||||
F test/tt3_core.c 8cd89ead95410f70e7fb02c79f1e040f9c5ad5cf
|
||||
F test/tt3_index.c 39eec10a35f57672225be4d182862152896dee4a
|
||||
F test/tt3_lookaside1.c 0377e202c3c2a50d688cb65ba203afeda6fafeb9
|
||||
F test/tt3_stress.c c57d804716165811d979d4a719e05baccd79277f
|
||||
@@ -1565,7 +1582,7 @@ F test/upsert2.test 9c3cdbb1a890227f6504ce4b0e3de68f4cdfa16bb21d8641208a9239896c
|
||||
F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c
|
||||
F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5
|
||||
F test/upsertfault.test f21ca47740841fdb4d61acfa7b17646d773e67724fe8c185b71c018db8a94b35
|
||||
F test/uri.test 3481026f00ade6dfe8adb7acb6e1e47b04369568
|
||||
F test/uri.test a2becabcb9fe25d08d1ae49c0788f4a75dda97bfe4c8641c2d04e224faf7a6e2
|
||||
F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7
|
||||
F test/userauth01.test e740a2697a7b40d7c5003a7d7edaee16acd349a9
|
||||
F test/utf16align.test 54cd35a27c005a9b6e7815d887718780b6a462ae
|
||||
@@ -1601,8 +1618,19 @@ F test/vtab_alter.test 736e66fb5ec7b4fee58229aa3ada2f27ec58bc58c00edae4836890c37
|
||||
F test/vtab_err.test dcc8b7b9cb67522b3fe7a272c73856829dae4ab7fdb30399aea1b6981bda2b65
|
||||
F test/vtab_shared.test 5253bff2355a9a3f014c15337da7e177ab0ef8ad
|
||||
F test/vtabdrop.test 65d4cf6722972e5499bdaf0c0d70ee3b8133944a4e4bc31862563f32a7edca12
|
||||
F test/wal.test cdf0ca6cc0447520d19ef1c83287824ebeb3e82d75af856511ba96841a79fc9b
|
||||
F test/wal2.test 155b9efa999bdb38ce1cd729b9a4fcdbffd6b88be27f039bad1d2929d287d918
|
||||
F test/wal.test a233cd4733928a5aaef24738727c4094d561ea68089f93cccd1f6e9dec610212
|
||||
F test/wal2.test a225bafac35a47765b890bacdeb57e5e81039f21cc18a1e8ce88eb76e56b843c
|
||||
F test/wal2big.test 0b4ec526f9ca4bbabc355042c38045ae2e253fb46eb327bb7693d0122bc6968b
|
||||
F test/wal2concurrent.test 7fc3e570073683a2a28f42bda46ecf516f5bc82afd703c1fbf4aa38e18fb3361
|
||||
F test/wal2fault.test 2e8e60cacd5bcd451618aeffd05f676894d17202d3e2986e288d36e2c5993249
|
||||
F test/wal2lock.test 0ef98d72dc6bcf7711dedd684760488400d9a9a6eec0dc5d3822060437793552
|
||||
F test/wal2recover.test ba8f4bc9397c838734619f9e759bd98b00e355347b3cf80a2e677610d231d5d8
|
||||
F test/wal2recover2.test dcfedc46a0080aa6d37c762863da2f5c0ec941c46a6cfe85b412e792c08a2ac2
|
||||
F test/wal2rewrite.test 6ca6f631ffcf871240beab5f02608913fd075c6d0d31310b026c8383c65c9f9c
|
||||
F test/wal2rollback.test 23adc4a099b23f6aaea8b04fdca1c35861d887dd80f8be7da2d5273eb777e428
|
||||
F test/wal2savepoint.test 2c82bd6a6ee5066c156040d2e9c2415646fcf96116ae7ad127eaf0c0b4a85f22
|
||||
F test/wal2simple.test 96206c98bf64ab20ec00a1c0f6c709e258b98b39f2149889361f31966ce5a703
|
||||
F test/wal2snapshot.test f6c3945bea572fd8745774e95b8dca1e5832c696e2251bb0db33391ee567fe60
|
||||
F test/wal3.test 2a93004bc0fb2b5c29888964024695bade278ab2
|
||||
F test/wal4.test 4744e155cd6299c6bd99d3eab1c82f77db9cdb3c
|
||||
F test/wal5.test 9c11da7aeccd83a46d79a556ad11a18d3cb15aa9
|
||||
@@ -1611,7 +1639,7 @@ F test/wal64k.test 2a525c0f45d709bae3765c71045ccec5df7d100ccbd3a7860fdba46c9addb
|
||||
F test/wal7.test 2ae8f427d240099cc4b2dfef63cff44e2a68a1bd
|
||||
F test/wal8.test d9df3fba4caad5854ed69ed673c68482514203c8
|
||||
F test/wal9.test 378e76a9ad09cd9bee06c172ad3547b0129a6750
|
||||
F test/wal_common.tcl a98f17fba96206122eff624db0ab13ec377be4fe
|
||||
F test/wal_common.tcl 37902864b63794e9bf59393b8887e21a9cbd4db8
|
||||
F test/walbak.test 018d4e5a3d45c6298d11b99f09a8ef6876527946
|
||||
F test/walbig.test f437473a16cfb314867c6b5d1dbcd519e73e3434
|
||||
F test/walblock.test be48f3a75eff0b4456209f26b3ce186c2015497d
|
||||
@@ -1628,7 +1656,7 @@ F test/walnoshm.test 84ca10c544632a756467336b7c3b864d493ee496
|
||||
F test/waloverwrite.test dad2f26567f1b45174e54fbf9a8dc1cb876a7f03
|
||||
F test/walpersist.test 8c6b7e3ec1ba91b5e4dc4e0921d6d3f87cd356a6
|
||||
F test/walprotocol.test a112aba0b79e3adeaa485fed09484b32c654e97df58e454aa8489ac2cd57bf84
|
||||
F test/walprotocol2.test 7d3b6b4bf0b12f8007121b1e6ef714bc99101fb3b48e46371df1db868eebc131
|
||||
F test/walprotocol2.test 7e4bedd5ee83607e2928ac438bf7332a396b980d3e02aa0746509ce11ad1f13c
|
||||
F test/walro.test cb438d05ba0d191f10b688e39c4f0cd5b71569a1d1f4440e5bdf3c6880e08c20
|
||||
F test/walro2.test 0e79dd15cbdb4f482c01ea248373669c732414a726b357d04846a816afafb768
|
||||
F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68
|
||||
@@ -1697,7 +1725,7 @@ F test/zipfilefault.test 44d4d7a7f7cca7521d569d7f71026b241d65a6b1757aa409c1a1688
|
||||
F tool/GetFile.cs a15e08acb5dd7539b75ba23501581d7c2b462cb5
|
||||
F tool/GetTclKit.bat 8995df40c4209808b31f24de0b58f90930239a234f7591e3675d45bfbb990c5d
|
||||
F tool/Replace.cs 02c67258801c2fb5f63231e0ac0f220b4b36ba91
|
||||
F tool/addopcodes.tcl 0288d5b26b9b35f4cb5affb76eec63f1dfce117bbc2020066708069ef60b86ff
|
||||
F tool/addopcodes.tcl 251331be53533f11d9ba36c730b9baaa911df89a08da3043344d0d634dc4a48e
|
||||
F tool/build-all-msvc.bat c12328d06c45fec8baada5949e3d5af54bf8c887 x
|
||||
F tool/build-shell.sh 950f47c6174f1eea171319438b93ba67ff5bf367
|
||||
F tool/cg_anno.tcl f95b0006c52cf7f0496b506343415b6ee3cdcdd3 x
|
||||
@@ -1723,7 +1751,7 @@ F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439
|
||||
F tool/mkautoconfamal.sh 422fc365358a2e92876ffc62971a0ff28ed472fc8bcf9de0df921c736fdeca5e
|
||||
F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x
|
||||
F tool/mkctimec.tcl dd183b73ae1c28249669741c250525f0407e579a70482371668fd5f130d9feb3
|
||||
F tool/mkkeywordhash.c 1f7f2ac1d9f262c08b67faaca47e6a68262ff39113fa4b27d1db2843b4c33e73
|
||||
F tool/mkkeywordhash.c 1e5cfcf4cd692ae21e7555604218097a1ee79dad3e366496e5236876255bfcc1
|
||||
F tool/mkmsvcmin.tcl cad0c7b54d7dd92bc87d59f36d4cc4f070eb2e625f14159dc2f5c4204e6a13ea
|
||||
F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c
|
||||
F tool/mkopcodeh.tcl 352a4319c0ad869eb26442bf7c3b015aa15594c21f1cce5a6420dbe999367c21
|
||||
@@ -1768,6 +1796,8 @@ F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f
|
||||
F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43
|
||||
F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d
|
||||
F tool/symbols.sh c5a617b8c61a0926747a56c65f5671ef8ac0e148
|
||||
F tool/tserver.c 434336a4566415b6917d1b9cf1834cbb3a5b03756c6ad83bbe68f135f64c68e5
|
||||
F tool/tserver_test.tcl 54f4e543cbef41bf4b061e1774fa2af6c7ed89e5982316a3c10080fac6203b56
|
||||
F tool/varint.c 5d94cb5003db9dbbcbcc5df08d66f16071aee003
|
||||
F tool/vdbe-compress.tcl 5926c71f9c12d2ab73ef35c29376e756eb68361c
|
||||
F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f
|
||||
@@ -1795,7 +1825,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 3961995cb56b43f1753c53fc869edf336f1c17ec994d67dc74e44e8e02493894
|
||||
R 48b7968067e3b6a49f53de2bbc8d6738
|
||||
P 820ba1cc7e374409f27d0f1e62ed17c9670ce0e1f6e1d39a02fa8d48319745e8 fdbf97e611ed23f86dc0df9d53421fb04226af0d1c7171d55c11acd0dbe935f0
|
||||
R 610cbf661872da3534d28d987e52b3ad
|
||||
U dan
|
||||
Z 7551eb2ed08d86310fd67509eed9279f
|
||||
Z 9e1b3f1f4c1450011db0f9cc2ebfe96b
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0465d2fc0d3f8beaa1b6b0bd7bd51d69a524f30d889c9402e7d02cc06164a310
|
||||
41e742bd0f94894b096b30474fb548f58951756d1ea47d0afb717a54802888f4
|
||||
@@ -171,6 +171,12 @@ int sqlite3BitvecSet(Bitvec *p, u32 i){
|
||||
if( p==0 ) return SQLITE_OK;
|
||||
assert( i>0 );
|
||||
assert( i<=p->iSize );
|
||||
if( i>p->iSize || i==0 ){
|
||||
sqlite3_log(SQLITE_ERROR,
|
||||
"Bitvec: setting bit %d of bitvec size %d\n", (int)i, (int)p->iSize
|
||||
);
|
||||
abort();
|
||||
}
|
||||
i--;
|
||||
while((p->iSize > BITVEC_NBIT) && p->iDivisor) {
|
||||
u32 bin = i/p->iDivisor;
|
||||
|
||||
+616
-48
@@ -467,7 +467,240 @@ static void downgradeAllSharedCacheTableLocks(Btree *p){
|
||||
|
||||
#endif /* SQLITE_OMIT_SHARED_CACHE */
|
||||
|
||||
static void releasePage(MemPage *pPage); /* Forward reference */
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/*
|
||||
** The following structure - BtreePtrmap - stores the in-memory pointer map
|
||||
** used for newly allocated pages in CONCURRENT transactions. Such pages are
|
||||
** always allocated in a contiguous block (from the end of the file) starting
|
||||
** with page BtreePtrmap.iFirst.
|
||||
*/
|
||||
typedef struct RollbackEntry RollbackEntry;
|
||||
typedef struct PtrmapEntry PtrmapEntry;
|
||||
struct PtrmapEntry {
|
||||
Pgno parent;
|
||||
u8 eType;
|
||||
};
|
||||
struct RollbackEntry {
|
||||
Pgno pgno;
|
||||
Pgno parent;
|
||||
u8 eType;
|
||||
};
|
||||
struct BtreePtrmap {
|
||||
Pgno iFirst; /* First new page number aPtr[0] */
|
||||
|
||||
int nPtrAlloc; /* Allocated size of aPtr[] array */
|
||||
PtrmapEntry *aPtr; /* Array of parent page numbers */
|
||||
|
||||
int nSvpt; /* Used size of aSvpt[] array */
|
||||
int nSvptAlloc; /* Allocated size of aSvpt[] */
|
||||
int *aSvpt; /* First aRollback[] entry for savepoint i */
|
||||
|
||||
int nRollback; /* Used size of aRollback[] array */
|
||||
int nRollbackAlloc; /* Allocated size of aRollback[] array */
|
||||
RollbackEntry *aRollback; /* Array of rollback entries */
|
||||
};
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** If page number pgno is greater than or equal to BtreePtrmap.iFirst,
|
||||
** store an entry for it in the pointer-map structure.
|
||||
*/
|
||||
static int btreePtrmapStore(
|
||||
BtShared *pBt,
|
||||
Pgno pgno,
|
||||
u8 eType,
|
||||
Pgno parent
|
||||
){
|
||||
BtreePtrmap *pMap = pBt->pMap;
|
||||
if( pgno>=pMap->iFirst ){
|
||||
int iEntry = pgno - pMap->iFirst;
|
||||
|
||||
/* Grow the aPtr[] array as required */
|
||||
while( iEntry>=pMap->nPtrAlloc ){
|
||||
int nNew = pMap->nPtrAlloc ? pMap->nPtrAlloc*2 : 16;
|
||||
PtrmapEntry *aNew = (PtrmapEntry*)sqlite3_realloc(
|
||||
pMap->aPtr, nNew*sizeof(PtrmapEntry)
|
||||
);
|
||||
if( aNew==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
}else{
|
||||
int nByte = (nNew-pMap->nPtrAlloc)*sizeof(PtrmapEntry);
|
||||
memset(&aNew[pMap->nPtrAlloc], 0, nByte);
|
||||
pMap->aPtr = aNew;
|
||||
pMap->nPtrAlloc = nNew;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add an entry to the rollback log if required */
|
||||
if( pMap->nSvpt>0 && pMap->aPtr[iEntry].parent ){
|
||||
if( pMap->nRollback>=pMap->nRollbackAlloc ){
|
||||
int nNew = pMap->nRollback ? pMap->nRollback*2 : 16;
|
||||
RollbackEntry *aNew = (RollbackEntry*)sqlite3_realloc(
|
||||
pMap->aRollback, nNew*sizeof(RollbackEntry)
|
||||
);
|
||||
if( aNew==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
}else{
|
||||
pMap->aRollback = aNew;
|
||||
pMap->nRollbackAlloc = nNew;
|
||||
}
|
||||
}
|
||||
|
||||
pMap->aRollback[pMap->nRollback].pgno = pgno;
|
||||
pMap->aRollback[pMap->nRollback].parent = pMap->aPtr[iEntry].parent;
|
||||
pMap->aRollback[pMap->nRollback].eType = pMap->aPtr[iEntry].eType;
|
||||
pMap->nRollback++;
|
||||
}
|
||||
|
||||
/* Update the aPtr[] array */
|
||||
pMap->aPtr[iEntry].parent = parent;
|
||||
pMap->aPtr[iEntry].eType = eType;
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** Open savepoint iSavepoint, if it is not already open.
|
||||
*/
|
||||
static int btreePtrmapBegin(BtShared *pBt, int nSvpt){
|
||||
BtreePtrmap *pMap = pBt->pMap;
|
||||
if( pMap && nSvpt>pMap->nSvpt ){
|
||||
int i;
|
||||
if( nSvpt>=pMap->nSvptAlloc ){
|
||||
int nNew = pMap->nSvptAlloc ? pMap->nSvptAlloc*2 : 16;
|
||||
int *aNew = sqlite3_realloc(pMap->aSvpt, sizeof(int) * nNew);
|
||||
if( aNew==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
}else{
|
||||
pMap->aSvpt = aNew;
|
||||
pMap->nSvptAlloc = nNew;
|
||||
}
|
||||
}
|
||||
|
||||
for(i=pMap->nSvpt; i<nSvpt; i++){
|
||||
pMap->aSvpt[i] = pMap->nRollback;
|
||||
}
|
||||
pMap->nSvpt = nSvpt;
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** Rollback (if op==SAVEPOINT_ROLLBACK) or release (if op==SAVEPOINT_RELEASE)
|
||||
** savepoint iSvpt.
|
||||
*/
|
||||
static void btreePtrmapEnd(BtShared *pBt, int op, int iSvpt){
|
||||
BtreePtrmap *pMap = pBt->pMap;
|
||||
if( pMap ){
|
||||
assert( op==SAVEPOINT_ROLLBACK || op==SAVEPOINT_RELEASE );
|
||||
assert( iSvpt>=0 || (iSvpt==-1 && op==SAVEPOINT_ROLLBACK) );
|
||||
if( iSvpt<0 ){
|
||||
pMap->nSvpt = 0;
|
||||
pMap->nRollback = 0;
|
||||
memset(pMap->aPtr, 0, sizeof(Pgno) * pMap->nPtrAlloc);
|
||||
}else if( iSvpt<pMap->nSvpt ){
|
||||
if( op==SAVEPOINT_ROLLBACK ){
|
||||
int ii;
|
||||
for(ii=pMap->nRollback-1; ii>=pMap->aSvpt[iSvpt]; ii--){
|
||||
RollbackEntry *p = &pMap->aRollback[ii];
|
||||
PtrmapEntry *pEntry = &pMap->aPtr[p->pgno - pMap->iFirst];
|
||||
pEntry->parent = p->parent;
|
||||
pEntry->eType = p->eType;
|
||||
}
|
||||
}
|
||||
pMap->nSvpt = iSvpt + (op==SAVEPOINT_ROLLBACK);
|
||||
pMap->nRollback = pMap->aSvpt[iSvpt];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** This function is called after an CONCURRENT transaction is opened on the
|
||||
** database. It allocates the BtreePtrmap structure used to track pointers
|
||||
** to allocated pages and zeroes the nFree/iTrunk fields in the database
|
||||
** header on page 1.
|
||||
*/
|
||||
static int btreePtrmapAllocate(BtShared *pBt){
|
||||
int rc = SQLITE_OK;
|
||||
if( pBt->pMap==0 ){
|
||||
BtreePtrmap *pMap = sqlite3_malloc(sizeof(BtreePtrmap));
|
||||
if( pMap==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
memset(&pBt->pPage1->aData[32], 0, sizeof(u32)*2);
|
||||
memset(pMap, 0, sizeof(BtreePtrmap));
|
||||
pMap->iFirst = pBt->nPage + 1;
|
||||
pBt->pMap = pMap;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** Free any BtreePtrmap structure allocated by an earlier call to
|
||||
** btreePtrmapAllocate().
|
||||
*/
|
||||
static void btreePtrmapDelete(BtShared *pBt){
|
||||
BtreePtrmap *pMap = pBt->pMap;
|
||||
if( pMap ){
|
||||
sqlite3_free(pMap->aRollback);
|
||||
sqlite3_free(pMap->aPtr);
|
||||
sqlite3_free(pMap->aSvpt);
|
||||
sqlite3_free(pMap);
|
||||
pBt->pMap = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Check that the pointer-map does not contain any entries with a parent
|
||||
** page of 0. Call sqlite3_log() multiple times to output the entire
|
||||
** data structure if it does.
|
||||
*/
|
||||
static void btreePtrmapCheck(BtShared *pBt, Pgno nPage){
|
||||
Pgno i;
|
||||
int bProblem = 0;
|
||||
BtreePtrmap *p = pBt->pMap;
|
||||
|
||||
for(i=p->iFirst; i<=nPage; i++){
|
||||
PtrmapEntry *pEntry = &p->aPtr[i-p->iFirst];
|
||||
if( pEntry->eType==PTRMAP_OVERFLOW1
|
||||
|| pEntry->eType==PTRMAP_OVERFLOW2
|
||||
|| pEntry->eType==PTRMAP_BTREE
|
||||
){
|
||||
if( pEntry->parent==0 ){
|
||||
bProblem = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( bProblem ){
|
||||
for(i=p->iFirst; i<=nPage; i++){
|
||||
PtrmapEntry *pEntry = &p->aPtr[i-p->iFirst];
|
||||
sqlite3_log(SQLITE_CORRUPT,
|
||||
"btreePtrmapCheck: pgno=%d eType=%d parent=%d",
|
||||
(int)i, (int)pEntry->eType, (int)pEntry->parent
|
||||
);
|
||||
}
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
#else /* SQLITE_OMIT_CONCURRENT */
|
||||
# define btreePtrmapAllocate(x) SQLITE_OK
|
||||
# define btreePtrmapDelete(x)
|
||||
# define btreePtrmapBegin(x,y) SQLITE_OK
|
||||
# define btreePtrmapEnd(x,y,z)
|
||||
# define btreePtrmapCheck(y,z)
|
||||
#endif /* SQLITE_OMIT_CONCURRENT */
|
||||
|
||||
static void releasePage(MemPage *pPage); /* Forward reference */
|
||||
static void releasePageOne(MemPage *pPage); /* Forward reference */
|
||||
static void releasePageNotNull(MemPage *pPage); /* Forward reference */
|
||||
|
||||
@@ -983,9 +1216,16 @@ static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){
|
||||
if( *pRC ) return;
|
||||
|
||||
assert( sqlite3_mutex_held(pBt->mutex) );
|
||||
/* The master-journal page number must never be used as a pointer map page */
|
||||
/* The master-journal page number is never added to a pointer-map page */
|
||||
assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) );
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
if( pBt->pMap ){
|
||||
*pRC = btreePtrmapStore(pBt, key, eType, parent);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
assert( pBt->autoVacuum );
|
||||
if( key==0 ){
|
||||
*pRC = SQLITE_CORRUPT_BKPT;
|
||||
@@ -2154,6 +2394,17 @@ getAndInitPage_error:
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/*
|
||||
** Set the value of the MemPage.pgnoRoot variable, if it exists.
|
||||
*/
|
||||
static void setMempageRoot(MemPage *pPg, u32 pgnoRoot){
|
||||
pPg->pgnoRoot = pgnoRoot;
|
||||
}
|
||||
#else
|
||||
# define setMempageRoot(x,y)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Release a MemPage. This should be called once for each prior
|
||||
** call to btreeGetPage.
|
||||
@@ -3046,10 +3297,10 @@ static int lockBtree(BtShared *pBt){
|
||||
goto page1_init_failed;
|
||||
}
|
||||
#else
|
||||
if( page1[18]>2 ){
|
||||
if( page1[18]>3 ){
|
||||
pBt->btsFlags |= BTS_READ_ONLY;
|
||||
}
|
||||
if( page1[19]>2 ){
|
||||
if( page1[19]>3 ){
|
||||
goto page1_init_failed;
|
||||
}
|
||||
|
||||
@@ -3061,9 +3312,9 @@ static int lockBtree(BtShared *pBt){
|
||||
** may not be the latest version - there may be a newer one in the log
|
||||
** file.
|
||||
*/
|
||||
if( page1[19]==2 && (pBt->btsFlags & BTS_NO_WAL)==0 ){
|
||||
if( page1[19]>=2 && (pBt->btsFlags & BTS_NO_WAL)==0 ){
|
||||
int isOpen = 0;
|
||||
rc = sqlite3PagerOpenWal(pBt->pPager, &isOpen);
|
||||
rc = sqlite3PagerOpenWal(pBt->pPager, (page1[19]==3), &isOpen);
|
||||
if( rc!=SQLITE_OK ){
|
||||
goto page1_init_failed;
|
||||
}else{
|
||||
@@ -3315,6 +3566,7 @@ int sqlite3BtreeNewDb(Btree *p){
|
||||
int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){
|
||||
BtShared *pBt = p->pBt;
|
||||
int rc = SQLITE_OK;
|
||||
int bConcurrent = (p->db->bConcurrent && !ISAUTOVACUUM);
|
||||
|
||||
sqlite3BtreeEnter(p);
|
||||
btreeIntegrity(p);
|
||||
@@ -3390,7 +3642,8 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){
|
||||
if( (pBt->btsFlags & BTS_READ_ONLY)!=0 ){
|
||||
rc = SQLITE_READONLY;
|
||||
}else{
|
||||
rc = sqlite3PagerBegin(pBt->pPager,wrflag>1,sqlite3TempInMemory(p->db));
|
||||
int exFlag = bConcurrent ? -1 : (wrflag>1);
|
||||
rc = sqlite3PagerBegin(pBt->pPager, exFlag, sqlite3TempInMemory(p->db));
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = newDatabase(pBt);
|
||||
}else if( rc==SQLITE_BUSY_SNAPSHOT && pBt->inTransaction==TRANS_NONE ){
|
||||
@@ -3450,6 +3703,15 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){
|
||||
}
|
||||
|
||||
trans_begun:
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
if( bConcurrent && rc==SQLITE_OK && sqlite3PagerIsWal(pBt->pPager) ){
|
||||
rc = sqlite3PagerBeginConcurrent(pBt->pPager);
|
||||
if( rc==SQLITE_OK && wrflag ){
|
||||
rc = btreePtrmapAllocate(pBt);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
if( pSchemaVersion ){
|
||||
*pSchemaVersion = get4byte(&pBt->pPage1->aData[40]);
|
||||
@@ -3459,7 +3721,11 @@ trans_begun:
|
||||
** open savepoints. If the second parameter is greater than 0 and
|
||||
** the sub-journal is not already open, then it will be opened here.
|
||||
*/
|
||||
rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint);
|
||||
int nSavepoint = p->db->nSavepoint;
|
||||
rc = sqlite3PagerOpenSavepoint(pBt->pPager, nSavepoint);
|
||||
if( rc==SQLITE_OK && nSavepoint ){
|
||||
rc = btreePtrmapBegin(pBt, nSavepoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3892,6 +4158,175 @@ static int autoVacuumCommit(BtShared *pBt){
|
||||
# define setChildPtrmaps(x) SQLITE_OK
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/*
|
||||
** This function is called as part of merging an CONCURRENT transaction with
|
||||
** the snapshot at the head of the wal file. It relocates all pages in the
|
||||
** range iFirst..iLast, inclusive. It is assumed that the BtreePtrmap
|
||||
** structure at BtShared.pMap contains the location of the pointers to each
|
||||
** page in the range.
|
||||
**
|
||||
** If pnCurrent is NULL, then all pages in the range are moved to currently
|
||||
** free locations (i.e. free-list entries) within the database file before page
|
||||
** iFirst.
|
||||
**
|
||||
** Or, if pnCurrent is not NULL, then it points to a value containing the
|
||||
** current size of the database file in pages. In this case, all pages are
|
||||
** relocated to the end of the database file - page iFirst is relocated to
|
||||
** page (*pnCurrent+1), page iFirst+1 to page (*pnCurrent+2), and so on.
|
||||
** Value *pnCurrent is set to the new size of the database before this
|
||||
** function returns.
|
||||
**
|
||||
** If no error occurs, SQLITE_OK is returned. Otherwise, an SQLite error code.
|
||||
*/
|
||||
static int btreeRelocateRange(
|
||||
BtShared *pBt, /* B-tree handle */
|
||||
Pgno iFirst, /* First page to relocate */
|
||||
Pgno iLast, /* Last page to relocate */
|
||||
Pgno *pnCurrent /* If not NULL, IN/OUT: Database size */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
BtreePtrmap *pMap = pBt->pMap;
|
||||
Pgno iPg;
|
||||
|
||||
for(iPg=iFirst; iPg<=iLast && rc==SQLITE_OK; iPg++){
|
||||
MemPage *pFree = 0; /* Page allocated from free-list */
|
||||
MemPage *pPg = 0;
|
||||
Pgno iNew; /* New page number for pPg */
|
||||
PtrmapEntry *pEntry; /* Pointer map entry for page iPg */
|
||||
|
||||
if( iPg==PENDING_BYTE_PAGE(pBt) ) continue;
|
||||
pEntry = &pMap->aPtr[iPg - pMap->iFirst];
|
||||
|
||||
if( pEntry->eType==PTRMAP_FREEPAGE ){
|
||||
Pgno dummy;
|
||||
rc = allocateBtreePage(pBt, &pFree, &dummy, iPg, BTALLOC_EXACT);
|
||||
if( pFree ){
|
||||
assert( sqlite3PagerPageRefcount(pFree->pDbPage)==1 );
|
||||
sqlite3PcacheDrop(pFree->pDbPage);
|
||||
}
|
||||
assert( rc!=SQLITE_OK || dummy==iPg );
|
||||
}else if( pnCurrent ){
|
||||
btreeGetPage(pBt, iPg, &pPg, 0);
|
||||
assert( sqlite3PagerIswriteable(pPg->pDbPage) );
|
||||
assert( sqlite3PagerPageRefcount(pPg->pDbPage)==1 );
|
||||
iNew = ++(*pnCurrent);
|
||||
if( iNew==PENDING_BYTE_PAGE(pBt) ) iNew = ++(*pnCurrent);
|
||||
rc = relocatePage(pBt, pPg, pEntry->eType, pEntry->parent, iNew, 1);
|
||||
releasePageNotNull(pPg);
|
||||
}else{
|
||||
rc = allocateBtreePage(pBt, &pFree, &iNew, iFirst-1, BTALLOC_LE);
|
||||
assert( rc!=SQLITE_OK || iNew<iFirst );
|
||||
if( rc==SQLITE_OK ){
|
||||
releasePage(pFree);
|
||||
btreeGetPage(pBt, iPg, &pPg, 0);
|
||||
rc = relocatePage(pBt, pPg, pEntry->eType, pEntry->parent,iNew,1);
|
||||
releasePage(pPg);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** The b-tree handle passed as the only argument is about to commit an
|
||||
** CONCURRENT transaction. At this point it is guaranteed that this is
|
||||
** possible - the wal WRITER lock is held and it is known that there are
|
||||
** no conflicts with committed transactions.
|
||||
*/
|
||||
static int btreeFixUnlocked(Btree *p){
|
||||
BtShared *pBt = p->pBt;
|
||||
MemPage *pPage1 = pBt->pPage1;
|
||||
u8 *p1 = pPage1->aData;
|
||||
Pager *pPager = pBt->pPager;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
/* If page 1 of the database is not writable, then no pages were allocated
|
||||
** or freed by this transaction. In this case no special handling is
|
||||
** required. Otherwise, if page 1 is dirty, proceed. */
|
||||
BtreePtrmap *pMap = pBt->pMap;
|
||||
Pgno iTrunk = get4byte(&p1[32]);
|
||||
Pgno nPage = btreePagecount(pBt);
|
||||
u32 nFree = get4byte(&p1[36]);
|
||||
|
||||
assert( pBt->pMap );
|
||||
rc = sqlite3PagerUpgradeSnapshot(pPager, pPage1->pDbPage);
|
||||
assert( p1==pPage1->aData );
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
Pgno nHPage = get4byte(&p1[28]);
|
||||
Pgno nFin = nHPage; /* Size of db after transaction merge */
|
||||
|
||||
if( sqlite3PagerIswriteable(pPage1->pDbPage) ){
|
||||
Pgno iHTrunk = get4byte(&p1[32]);
|
||||
u32 nHFree = get4byte(&p1[36]);
|
||||
|
||||
btreePtrmapCheck(pBt, nPage);
|
||||
|
||||
/* Attach the head database free list to the end of the current
|
||||
** transactions free-list (if any). */
|
||||
if( iTrunk!=0 ){
|
||||
put4byte(&p1[36], nHFree + nFree);
|
||||
put4byte(&p1[32], iTrunk);
|
||||
while( iTrunk ){
|
||||
DbPage *pTrunk = sqlite3PagerLookup(pPager, iTrunk);
|
||||
iTrunk = get4byte((u8*)pTrunk->pData);
|
||||
if( iTrunk==0 ){
|
||||
put4byte((u8*)pTrunk->pData, iHTrunk);
|
||||
}
|
||||
sqlite3PagerUnref(pTrunk);
|
||||
};
|
||||
}
|
||||
|
||||
if( nHPage<(pMap->iFirst-1) ){
|
||||
/* The database consisted of (pMap->iFirst-1) pages when the current
|
||||
** concurrent transaction was opened. And an concurrent transaction may
|
||||
** not be executed on an auto-vacuum database - so the db should
|
||||
** not have shrunk since the transaction was opened. Therefore nHPage
|
||||
** should be set to (pMap->iFirst-1) or greater. */
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
}else{
|
||||
/* The current transaction allocated pages pMap->iFirst through
|
||||
** nPage (inclusive) at the end of the database file. Meanwhile,
|
||||
** other transactions have allocated (iFirst..nHPage). So move
|
||||
** pages (iFirst..MIN(nPage,nHPage)) to (MAX(nPage,nHPage)+1). */
|
||||
Pgno iLast = MIN(nPage, nHPage); /* Last page to move */
|
||||
Pgno nCurrent; /* Current size of db */
|
||||
|
||||
nCurrent = MAX(nPage, nHPage);
|
||||
pBt->nPage = nCurrent;
|
||||
rc = btreeRelocateRange(pBt, pMap->iFirst, iLast, &nCurrent);
|
||||
|
||||
/* There are now no collisions with the snapshot at the head of the
|
||||
** database file. So at this point it would be possible to write
|
||||
** the transaction out to disk. Before doing so though, attempt to
|
||||
** relocate some of the new pages to free locations within the body
|
||||
** of the database file (i.e. free-list entries). */
|
||||
if( rc==SQLITE_OK ){
|
||||
assert( nCurrent!=PENDING_BYTE_PAGE(pBt) );
|
||||
sqlite3PagerSetDbsize(pBt->pPager, nCurrent);
|
||||
nFree = get4byte(&p1[36]);
|
||||
nFin = nCurrent-nFree;
|
||||
if( nCurrent>PENDING_BYTE_PAGE(pBt) && nFin<=PENDING_BYTE_PAGE(pBt) ){
|
||||
nFin--;
|
||||
}
|
||||
nFin = MAX(nFin, nHPage);
|
||||
rc = btreeRelocateRange(pBt, nFin+1, nCurrent, 0);
|
||||
}
|
||||
|
||||
put4byte(&p1[28], nFin);
|
||||
}
|
||||
}
|
||||
sqlite3PagerSetDbsize(pPager, nFin);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
# define btreeFixUnlocked(X) SQLITE_OK
|
||||
#endif /* SQLITE_OMIT_CONCURRENT */
|
||||
|
||||
/*
|
||||
** This routine does the first phase of a two-phase commit. This routine
|
||||
** causes a rollback journal to be created (if it does not already exist)
|
||||
@@ -3923,8 +4358,10 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
|
||||
if( p->inTrans==TRANS_WRITE ){
|
||||
BtShared *pBt = p->pBt;
|
||||
sqlite3BtreeEnter(p);
|
||||
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
if( pBt->autoVacuum ){
|
||||
assert( ISCONCURRENT==0 );
|
||||
rc = autoVacuumCommit(pBt);
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3BtreeLeave(p);
|
||||
@@ -3935,7 +4372,12 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
|
||||
sqlite3PagerTruncateImage(pBt->pPager, pBt->nPage);
|
||||
}
|
||||
#endif
|
||||
rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0);
|
||||
if( rc==SQLITE_OK && ISCONCURRENT ){
|
||||
rc = btreeFixUnlocked(p);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0);
|
||||
}
|
||||
sqlite3BtreeLeave(p);
|
||||
}
|
||||
return rc;
|
||||
@@ -3978,6 +4420,11 @@ static void btreeEndTransaction(Btree *p){
|
||||
unlockBtreeIfUnused(pBt);
|
||||
}
|
||||
|
||||
/* If this was an CONCURRENT transaction, delete the pBt->pMap object.
|
||||
** Also call PagerEndConcurrent() to ensure that the pager has discarded
|
||||
** the record of all pages read within the transaction. */
|
||||
btreePtrmapDelete(pBt);
|
||||
sqlite3PagerEndConcurrent(pBt->pPager);
|
||||
btreeIntegrity(p);
|
||||
}
|
||||
|
||||
@@ -4199,6 +4646,9 @@ int sqlite3BtreeBeginStmt(Btree *p, int iStatement){
|
||||
** such savepoints while the statement transaction savepoint is active.
|
||||
*/
|
||||
rc = sqlite3PagerOpenSavepoint(pBt->pPager, iStatement);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = btreePtrmapBegin(pBt, iStatement);
|
||||
}
|
||||
sqlite3BtreeLeave(p);
|
||||
return rc;
|
||||
}
|
||||
@@ -4222,6 +4672,7 @@ int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){
|
||||
assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK );
|
||||
assert( iSavepoint>=0 || (iSavepoint==-1 && op==SAVEPOINT_ROLLBACK) );
|
||||
sqlite3BtreeEnter(p);
|
||||
btreePtrmapEnd(pBt, op, iSavepoint);
|
||||
if( op==SAVEPOINT_ROLLBACK ){
|
||||
rc = saveAllCursors(pBt, 0, 0);
|
||||
}
|
||||
@@ -4964,6 +5415,7 @@ const void *sqlite3BtreePayloadFetch(BtCursor *pCur, u32 *pAmt){
|
||||
*/
|
||||
static int moveToChild(BtCursor *pCur, u32 newPgno){
|
||||
BtShared *pBt = pCur->pBt;
|
||||
int rc;
|
||||
|
||||
assert( cursorOwnsBtShared(pCur) );
|
||||
assert( pCur->eState==CURSOR_VALID );
|
||||
@@ -4978,7 +5430,12 @@ static int moveToChild(BtCursor *pCur, u32 newPgno){
|
||||
pCur->apPage[pCur->iPage] = pCur->pPage;
|
||||
pCur->ix = 0;
|
||||
pCur->iPage++;
|
||||
return getAndInitPage(pBt, newPgno, &pCur->pPage, pCur, pCur->curPagerFlags);
|
||||
rc = getAndInitPage(pBt, newPgno, &pCur->pPage,
|
||||
pCur, pCur->curPagerFlags);
|
||||
if( rc==SQLITE_OK ){
|
||||
setMempageRoot(pCur->pPage, pCur->pgnoRoot);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
@@ -5090,6 +5547,7 @@ static int moveToRoot(BtCursor *pCur){
|
||||
pCur->eState = CURSOR_INVALID;
|
||||
return rc;
|
||||
}
|
||||
setMempageRoot(pCur->pPage, pCur->pgnoRoot);
|
||||
pCur->iPage = 0;
|
||||
pCur->curIntKey = pCur->pPage->intKey;
|
||||
}
|
||||
@@ -5821,7 +6279,7 @@ static int allocateBtreePage(
|
||||
Pgno mxPage; /* Total size of the database file */
|
||||
|
||||
assert( sqlite3_mutex_held(pBt->mutex) );
|
||||
assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) );
|
||||
assert( eMode==BTALLOC_ANY || (nearby>0 && REQUIRE_PTRMAP ) );
|
||||
pPage1 = pBt->pPage1;
|
||||
mxPage = btreePagecount(pBt);
|
||||
/* EVIDENCE-OF: R-05119-02637 The 4-byte big-endian integer at offset 36
|
||||
@@ -5831,6 +6289,15 @@ static int allocateBtreePage(
|
||||
if( n>=mxPage ){
|
||||
return SQLITE_CORRUPT_BKPT;
|
||||
}
|
||||
|
||||
/* Ensure page 1 is writable. This function will either change the number
|
||||
** of pages in the free-list or the size of the database file. Since both
|
||||
** of these operations involve modifying page 1 header fields, page 1
|
||||
** will definitely be written by this transaction. If this is an CONCURRENT
|
||||
** transaction, ensure the BtreePtrmap structure has been allocated. */
|
||||
rc = sqlite3PagerWrite(pPage1->pDbPage);
|
||||
if( rc ) return rc;
|
||||
|
||||
if( n>0 ){
|
||||
/* There are pages on the freelist. Reuse one of those pages. */
|
||||
Pgno iTrunk;
|
||||
@@ -5841,28 +6308,29 @@ static int allocateBtreePage(
|
||||
** shows that the page 'nearby' is somewhere on the free-list, then
|
||||
** the entire-list will be searched for that page.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
if( eMode==BTALLOC_EXACT ){
|
||||
if( nearby<=mxPage ){
|
||||
u8 eType;
|
||||
assert( nearby>0 );
|
||||
assert( pBt->autoVacuum );
|
||||
rc = ptrmapGet(pBt, nearby, &eType, 0);
|
||||
if( rc ) return rc;
|
||||
if( eType==PTRMAP_FREEPAGE ){
|
||||
searchList = 1;
|
||||
assert( ISAUTOVACUUM!=ISCONCURRENT );
|
||||
if( ISAUTOVACUUM ){
|
||||
if( nearby<=mxPage ){
|
||||
u8 eType;
|
||||
assert( nearby>0 );
|
||||
assert( pBt->autoVacuum );
|
||||
rc = ptrmapGet(pBt, nearby, &eType, 0);
|
||||
if( rc ) return rc;
|
||||
if( eType==PTRMAP_FREEPAGE ){
|
||||
searchList = 1;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
searchList = 1;
|
||||
}
|
||||
}else if( eMode==BTALLOC_LE ){
|
||||
searchList = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Decrement the free-list count by 1. Set iTrunk to the index of the
|
||||
** first free-list trunk page. iPrevTrunk is initially 1.
|
||||
*/
|
||||
rc = sqlite3PagerWrite(pPage1->pDbPage);
|
||||
if( rc ) return rc;
|
||||
put4byte(&pPage1->aData[36], n-1);
|
||||
|
||||
/* The code within this loop is run only once if the 'searchList' variable
|
||||
@@ -6168,7 +6636,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
|
||||
/* If the database supports auto-vacuum, write an entry in the pointer-map
|
||||
** to indicate that the page is free.
|
||||
*/
|
||||
if( ISAUTOVACUUM ){
|
||||
if( REQUIRE_PTRMAP ){
|
||||
ptrmapPut(pBt, iPage, PTRMAP_FREEPAGE, 0, &rc);
|
||||
if( rc ) goto freepage_out;
|
||||
}
|
||||
@@ -6484,7 +6952,7 @@ static int fillInCell(
|
||||
}
|
||||
#endif
|
||||
rc = allocateBtreePage(pBt, &pOvfl, &pgnoOvfl, pgnoOvfl, 0);
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
|
||||
/* If the database supports auto-vacuum, and the second or subsequent
|
||||
** overflow page is being allocated, add an entry to the pointer-map
|
||||
** for that page now.
|
||||
@@ -6495,14 +6963,13 @@ static int fillInCell(
|
||||
** may misinterpret the uninitialized values and delete the
|
||||
** wrong pages from the database.
|
||||
*/
|
||||
if( pBt->autoVacuum && rc==SQLITE_OK ){
|
||||
if( REQUIRE_PTRMAP && rc==SQLITE_OK ){
|
||||
u8 eType = (pgnoPtrmap?PTRMAP_OVERFLOW2:PTRMAP_OVERFLOW1);
|
||||
ptrmapPut(pBt, pgnoOvfl, eType, pgnoPtrmap, &rc);
|
||||
if( rc ){
|
||||
releasePage(pOvfl);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if( rc ){
|
||||
releasePage(pToRelease);
|
||||
return rc;
|
||||
@@ -6644,6 +7111,7 @@ static void insertCell(
|
||||
assert( j==0 || pPage->aiOvfl[j-1]<(u16)i ); /* Overflows in sorted order */
|
||||
assert( j==0 || i==pPage->aiOvfl[j-1]+1 ); /* Overflows are sequential */
|
||||
}else{
|
||||
BtShared *pBt = pPage->pBt;
|
||||
int rc = sqlite3PagerWrite(pPage->pDbPage);
|
||||
if( rc!=SQLITE_OK ){
|
||||
*pRC = rc;
|
||||
@@ -6658,7 +7126,7 @@ static void insertCell(
|
||||
** if it returns successfully */
|
||||
assert( idx >= 0 );
|
||||
assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB );
|
||||
assert( idx+sz <= (int)pPage->pBt->usableSize );
|
||||
assert( idx+sz <= (int)pBt->usableSize );
|
||||
pPage->nFree -= (u16)(2 + sz);
|
||||
memcpy(&data[idx], pCell, sz);
|
||||
if( iChild ){
|
||||
@@ -6671,14 +7139,12 @@ static void insertCell(
|
||||
/* increment the cell count */
|
||||
if( (++data[pPage->hdrOffset+4])==0 ) data[pPage->hdrOffset+3]++;
|
||||
assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell );
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
if( pPage->pBt->autoVacuum ){
|
||||
if( REQUIRE_PTRMAP ){
|
||||
/* The cell may contain a pointer to an overflow page. If so, write
|
||||
** the entry for the overflow page into the pointer map.
|
||||
*/
|
||||
ptrmapPutOvflPtr(pPage, pCell, pRC);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7089,7 +7555,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){
|
||||
** be marked as dirty. Returning an error code will cause a
|
||||
** rollback, undoing any changes made to the parent page.
|
||||
*/
|
||||
if( ISAUTOVACUUM ){
|
||||
if( REQUIRE_PTRMAP ){
|
||||
ptrmapPut(pBt, pgnoNew, PTRMAP_BTREE, pParent->pgno, &rc);
|
||||
if( szCell>pNew->minLocal ){
|
||||
ptrmapPutOvflPtr(pNew, pCell, &rc);
|
||||
@@ -7226,7 +7692,7 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){
|
||||
/* If this is an auto-vacuum database, update the pointer-map entries
|
||||
** for any b-tree or overflow pages that pTo now contains the pointers to.
|
||||
*/
|
||||
if( ISAUTOVACUUM ){
|
||||
if( REQUIRE_PTRMAP ){
|
||||
*pRC = setChildPtrmaps(pTo);
|
||||
}
|
||||
}
|
||||
@@ -7277,7 +7743,8 @@ static int balance_nonroot(
|
||||
int iParentIdx, /* Index of "the page" in pParent */
|
||||
u8 *aOvflSpace, /* page-size bytes of space for parent ovfl */
|
||||
int isRoot, /* True if pParent is a root-page */
|
||||
int bBulk /* True if this call is part of a bulk load */
|
||||
int bBulk, /* True if this call is part of a bulk load */
|
||||
Pgno pgnoRoot /* Root page of b-tree being balanced */
|
||||
){
|
||||
BtShared *pBt; /* The whole database */
|
||||
int nMaxCells = 0; /* Allocated size of apCell, szCell, aFrom. */
|
||||
@@ -7369,6 +7836,8 @@ static int balance_nonroot(
|
||||
memset(apOld, 0, (i+1)*sizeof(MemPage*));
|
||||
goto balance_cleanup;
|
||||
}
|
||||
setMempageRoot(apOld[i], pgnoRoot);
|
||||
|
||||
nMaxCells += 1+apOld[i]->nCell+apOld[i]->nOverflow;
|
||||
if( (i--)==0 ) break;
|
||||
|
||||
@@ -7689,7 +8158,7 @@ static int balance_nonroot(
|
||||
cntOld[i] = b.nCell;
|
||||
|
||||
/* Set the pointer-map entry for the new sibling page. */
|
||||
if( ISAUTOVACUUM ){
|
||||
if( REQUIRE_PTRMAP ){
|
||||
ptrmapPut(pBt, pNew->pgno, PTRMAP_BTREE, pParent->pgno, &rc);
|
||||
if( rc!=SQLITE_OK ){
|
||||
goto balance_cleanup;
|
||||
@@ -7783,7 +8252,7 @@ static int balance_nonroot(
|
||||
** updated. This happens below, after the sibling pages have been
|
||||
** populated, not here.
|
||||
*/
|
||||
if( ISAUTOVACUUM ){
|
||||
if( REQUIRE_PTRMAP ){
|
||||
MemPage *pNew = apNew[0];
|
||||
u8 *aOld = pNew->aData;
|
||||
int cntOldNext = pNew->nCell + pNew->nOverflow;
|
||||
@@ -7967,7 +8436,7 @@ static int balance_nonroot(
|
||||
);
|
||||
copyNodeContent(apNew[0], pParent, &rc);
|
||||
freePage(apNew[0], &rc);
|
||||
}else if( ISAUTOVACUUM && !leafCorrection ){
|
||||
}else if( REQUIRE_PTRMAP && !leafCorrection ){
|
||||
/* Fix the pointer map entries associated with the right-child of each
|
||||
** sibling page. All other pointer map entries have already been taken
|
||||
** care of. */
|
||||
@@ -8050,7 +8519,7 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0);
|
||||
copyNodeContent(pRoot, pChild, &rc);
|
||||
if( ISAUTOVACUUM ){
|
||||
if( REQUIRE_PTRMAP ){
|
||||
ptrmapPut(pBt, pgnoChild, PTRMAP_BTREE, pRoot->pgno, &rc);
|
||||
}
|
||||
}
|
||||
@@ -8177,7 +8646,7 @@ static int balance(BtCursor *pCur){
|
||||
*/
|
||||
u8 *pSpace = sqlite3PageMalloc(pCur->pBt->pageSize);
|
||||
rc = balance_nonroot(pParent, iIdx, pSpace, iPage==1,
|
||||
pCur->hints&BTREE_BULKLOAD);
|
||||
pCur->hints&BTREE_BULKLOAD, pCur->pgnoRoot);
|
||||
if( pFree ){
|
||||
/* If pFree is not NULL, it points to the pSpace buffer used
|
||||
** by a previous call to balance_nonroot(). Its contents are
|
||||
@@ -8498,7 +8967,7 @@ int sqlite3BtreeInsert(
|
||||
}
|
||||
rc = clearCell(pPage, oldCell, &info);
|
||||
if( info.nSize==szNew && info.nLocal==info.nPayload
|
||||
&& (!ISAUTOVACUUM || szNew<pPage->minLocal)
|
||||
&& (!REQUIRE_PTRMAP || szNew<pPage->minLocal)
|
||||
){
|
||||
/* Overwrite the old cell with the new if they are the same size.
|
||||
** We could also try to do this if the old cell is smaller, then add
|
||||
@@ -8932,7 +9401,8 @@ static int clearDatabasePage(
|
||||
BtShared *pBt, /* The BTree that contains the table */
|
||||
Pgno pgno, /* Page number to clear */
|
||||
int freePageFlag, /* Deallocate page if true */
|
||||
int *pnChange /* Add number of Cells freed to this counter */
|
||||
int *pnChange, /* Add number of Cells freed to this counter */
|
||||
Pgno pgnoRoot
|
||||
){
|
||||
MemPage *pPage;
|
||||
int rc;
|
||||
@@ -8947,6 +9417,7 @@ static int clearDatabasePage(
|
||||
}
|
||||
rc = getAndInitPage(pBt, pgno, &pPage, 0, 0);
|
||||
if( rc ) return rc;
|
||||
setMempageRoot(pPage, pgnoRoot);
|
||||
if( pPage->bBusy ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
goto cleardatabasepage_out;
|
||||
@@ -8956,14 +9427,16 @@ static int clearDatabasePage(
|
||||
for(i=0; i<pPage->nCell; i++){
|
||||
pCell = findCell(pPage, i);
|
||||
if( !pPage->leaf ){
|
||||
rc = clearDatabasePage(pBt, get4byte(pCell), 1, pnChange);
|
||||
rc = clearDatabasePage(pBt, get4byte(pCell), 1, pnChange, pgnoRoot);
|
||||
if( rc ) goto cleardatabasepage_out;
|
||||
}
|
||||
rc = clearCell(pPage, pCell, &info);
|
||||
if( rc ) goto cleardatabasepage_out;
|
||||
}
|
||||
if( !pPage->leaf ){
|
||||
rc = clearDatabasePage(pBt, get4byte(&pPage->aData[hdr+8]), 1, pnChange);
|
||||
rc = clearDatabasePage(
|
||||
pBt, get4byte(&pPage->aData[hdr+8]), 1, pnChange, pgnoRoot
|
||||
);
|
||||
if( rc ) goto cleardatabasepage_out;
|
||||
}else if( pnChange ){
|
||||
assert( pPage->intKey || CORRUPT_DB );
|
||||
@@ -9008,7 +9481,7 @@ int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){
|
||||
** is the root of a table b-tree - if it is not, the following call is
|
||||
** a no-op). */
|
||||
invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1);
|
||||
rc = clearDatabasePage(pBt, (Pgno)iTable, 0, pnChange);
|
||||
rc = clearDatabasePage(pBt, (Pgno)iTable, 0, pnChange, (Pgno)iTable);
|
||||
}
|
||||
sqlite3BtreeLeave(p);
|
||||
return rc;
|
||||
@@ -9883,9 +10356,11 @@ char *sqlite3BtreeIntegrityCheck(
|
||||
}
|
||||
pBt->db->flags = savedDbFlags;
|
||||
|
||||
/* Make sure every page in the file is referenced
|
||||
*/
|
||||
for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){
|
||||
/* Make sure every page in the file is referenced. Skip this if the
|
||||
** database is currently being written by a CONCURRENT transaction (it
|
||||
** may fail as pages that were part of the free-list when the transaction
|
||||
** was opened cannot be counted). */
|
||||
for(i=1; ISCONCURRENT==0 && i<=sCheck.nPage && sCheck.mxErr; i++){
|
||||
#ifdef SQLITE_OMIT_AUTOVACUUM
|
||||
if( getPageReferenced(&sCheck, i)==0 ){
|
||||
checkAppendMsg(&sCheck, "Page %d is never used", i);
|
||||
@@ -10142,7 +10617,7 @@ int sqlite3BtreeSetVersion(Btree *pBtree, int iVersion){
|
||||
BtShared *pBt = pBtree->pBt;
|
||||
int rc; /* Return code */
|
||||
|
||||
assert( iVersion==1 || iVersion==2 );
|
||||
assert( iVersion==1 || iVersion==2 || iVersion==3 );
|
||||
|
||||
/* If setting the version fields to 1, do not automatically open the
|
||||
** WAL connection, even if the version fields are currently set to 2.
|
||||
@@ -10189,6 +10664,99 @@ int sqlite3BtreeIsReadonly(Btree *p){
|
||||
*/
|
||||
int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }
|
||||
|
||||
/*
|
||||
** This function is called to ensure that all locks required to commit the
|
||||
** current write-transaction to the database file are held. If the db is
|
||||
** in rollback mode, this means the EXCLUSIVE lock on the database file.
|
||||
**
|
||||
** Or, if this is an CONCURRENT transaction on a wal-mode database, the WRITER
|
||||
** lock on the wal file. In this case this function also checks that the
|
||||
** CONCURRENT transaction can be safely committed (does not commit with any
|
||||
** other transaction committed since it was opened).
|
||||
**
|
||||
** SQLITE_OK is returned if successful. SQLITE_BUSY if the required locks
|
||||
** cannot be obtained due to a conflicting lock. If the locks cannot be
|
||||
** obtained for an CONCURRENT transaction due to a conflict with an already
|
||||
** committed transaction, SQLITE_BUSY_SNAPSHOT is returned. Otherwise, if
|
||||
** some other error (OOM, IO, etc.) occurs, the relevant SQLite error code
|
||||
** is returned.
|
||||
*/
|
||||
int sqlite3BtreeExclusiveLock(Btree *p){
|
||||
int rc;
|
||||
Pgno pgno = 0;
|
||||
BtShared *pBt = p->pBt;
|
||||
assert( p->inTrans==TRANS_WRITE && pBt->pPage1 );
|
||||
sqlite3BtreeEnter(p);
|
||||
rc = sqlite3PagerExclusiveLock(pBt->pPager, pBt->pPage1->pDbPage, &pgno);
|
||||
#ifdef SQLITE_OMIT_CONCURRENT
|
||||
assert( pgno==0 );
|
||||
#else
|
||||
if( rc==SQLITE_BUSY_SNAPSHOT && pgno ){
|
||||
PgHdr *pPg = 0;
|
||||
int rc2 = sqlite3PagerGet(pBt->pPager, pgno, &pPg, 0);
|
||||
if( rc2==SQLITE_OK ){
|
||||
int bWrite = -1;
|
||||
const char *zObj = 0;
|
||||
const char *zTab = 0;
|
||||
char zContent[17];
|
||||
|
||||
if( pPg ){
|
||||
Pgno pgnoRoot = 0;
|
||||
HashElem *pE;
|
||||
Schema *pSchema;
|
||||
u8 *aData = (u8*)sqlite3PagerGetData(pPg);
|
||||
int i;
|
||||
for(i=0; i<8; i++){
|
||||
static const char hexdigits[] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
zContent[i*2] = hexdigits[(aData[i] >> 4)];
|
||||
zContent[i*2+1] = hexdigits[(aData[i] & 0xF)];
|
||||
}
|
||||
zContent[16] = '\0';
|
||||
|
||||
pgnoRoot = ((MemPage*)sqlite3PagerGetExtra(pPg))->pgnoRoot;
|
||||
bWrite = sqlite3PagerIswriteable(pPg);
|
||||
sqlite3PagerUnref(pPg);
|
||||
|
||||
pSchema = sqlite3SchemaGet(p->db, p);
|
||||
if( pSchema ){
|
||||
for(pE=sqliteHashFirst(&pSchema->tblHash); pE; pE=sqliteHashNext(pE)){
|
||||
Table *pTab = (Table *)sqliteHashData(pE);
|
||||
if( pTab->tnum==(int)pgnoRoot ){
|
||||
zObj = pTab->zName;
|
||||
zTab = 0;
|
||||
}else{
|
||||
Index *pIdx;
|
||||
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
||||
if( pIdx->tnum==(int)pgnoRoot ){
|
||||
zObj = pIdx->zName;
|
||||
zTab = pTab->zName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_log(SQLITE_OK,
|
||||
"cannot commit CONCURRENT transaction "
|
||||
"- conflict at page %d "
|
||||
"(%s page; part of db %s %s%s%s; content=%s...)",
|
||||
(int)pgno,
|
||||
(bWrite==0?"read-only":(bWrite>0?"read/write":"unknown")),
|
||||
(zTab ? "index" : "table"),
|
||||
(zTab ? zTab : ""), (zTab ? "." : ""), (zObj ? zObj : "UNKNOWN"),
|
||||
zContent
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
sqlite3BtreeLeave(p);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#if !defined(SQLITE_OMIT_SHARED_CACHE)
|
||||
/*
|
||||
** Return true if the Btree passed as the only argument is sharable.
|
||||
|
||||
+2
-1
@@ -331,6 +331,8 @@ int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask);
|
||||
int sqlite3BtreeIsReadonly(Btree *pBt);
|
||||
int sqlite3HeaderSizeBtree(void);
|
||||
|
||||
int sqlite3BtreeExclusiveLock(Btree *pBt);
|
||||
|
||||
#ifndef NDEBUG
|
||||
int sqlite3BtreeCursorIsValid(BtCursor*);
|
||||
#endif
|
||||
@@ -389,5 +391,4 @@ void sqlite3BtreeCursorList(Btree*);
|
||||
# define sqlite3SchemaMutexHeld(X,Y,Z) 1
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* SQLITE_BTREE_H */
|
||||
|
||||
+17
-3
@@ -232,6 +232,7 @@
|
||||
typedef struct MemPage MemPage;
|
||||
typedef struct BtLock BtLock;
|
||||
typedef struct CellInfo CellInfo;
|
||||
typedef struct BtreePtrmap BtreePtrmap;
|
||||
|
||||
/*
|
||||
** This is a magic string that appears at the beginning of every
|
||||
@@ -276,6 +277,9 @@ struct MemPage {
|
||||
u8 intKey; /* True if table b-trees. False for index b-trees */
|
||||
u8 intKeyLeaf; /* True if the leaf of an intKey table */
|
||||
Pgno pgno; /* Page number for this page */
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
Pgno pgnoRoot; /* Root page of b-tree that this page belongs to */
|
||||
#endif
|
||||
/* Only the first 8 bytes (above) are zeroed by pager.c when a new page
|
||||
** is allocated. All fields that follow must be initialized before use */
|
||||
u8 leaf; /* True if a leaf page */
|
||||
@@ -440,6 +444,9 @@ struct BtShared {
|
||||
Btree *pWriter; /* Btree with currently open write transaction */
|
||||
#endif
|
||||
u8 *pTmpSpace; /* Temp space sufficient to hold a single cell */
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
BtreePtrmap *pMap;
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -648,12 +655,19 @@ struct BtCursor {
|
||||
** (sqliteMallocRaw), it is not possible to use conditional compilation.
|
||||
** So, this macro is defined instead.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
#define ISAUTOVACUUM (pBt->autoVacuum)
|
||||
#else
|
||||
#ifdef SQLITE_OMIT_AUTOVACUUM
|
||||
#define ISAUTOVACUUM 0
|
||||
#else
|
||||
#define ISAUTOVACUUM (pBt->autoVacuum)
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_OMIT_CONCURRENT
|
||||
# define ISCONCURRENT 0
|
||||
#else
|
||||
# define ISCONCURRENT (pBt->pMap!=0)
|
||||
#endif
|
||||
|
||||
#define REQUIRE_PTRMAP (ISAUTOVACUUM || ISCONCURRENT)
|
||||
|
||||
/*
|
||||
** This structure is passed around through all the sanity checking routines
|
||||
|
||||
+4
-2
@@ -59,8 +59,10 @@ void sqlite3TableLock(
|
||||
TableLock *p;
|
||||
assert( iDb>=0 );
|
||||
|
||||
#ifdef SQLITE_OMIT_CONCURRENT
|
||||
if( iDb==1 ) return;
|
||||
if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return;
|
||||
#endif
|
||||
for(i=0; i<pToplevel->nTableLock; i++){
|
||||
p = &pToplevel->aTableLock[i];
|
||||
if( p->iDb==iDb && p->iTab==iTab ){
|
||||
@@ -4140,13 +4142,13 @@ void sqlite3BeginTransaction(Parse *pParse, int type){
|
||||
}
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
if( !v ) return;
|
||||
if( type!=TK_DEFERRED ){
|
||||
if( type==TK_IMMEDIATE || type==TK_EXCLUSIVE ){
|
||||
for(i=0; i<db->nDb; i++){
|
||||
sqlite3VdbeAddOp2(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1);
|
||||
sqlite3VdbeUsesBtree(v, i);
|
||||
}
|
||||
}
|
||||
sqlite3VdbeAddOp0(v, OP_AutoCommit);
|
||||
sqlite3VdbeAddOp3(v, OP_AutoCommit, 0, 0, (type==TK_CONCURRENT));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+29
@@ -4301,6 +4301,35 @@ void sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
|
||||
SQLITE_EXPERIMENTAL int sqlite3_wal_info(
|
||||
sqlite3 *db, const char *zDb,
|
||||
unsigned int *pnPrior, unsigned int *pnFrame
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
Btree *pBt;
|
||||
int iDb;
|
||||
|
||||
#ifdef SQLITE_ENABLE_API_ARMOR
|
||||
if( !sqlite3SafetyCheckOk(db) ){
|
||||
return SQLITE_MISUSE_BKPT;
|
||||
}
|
||||
#endif
|
||||
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
iDb = sqlite3FindDbName(db, zDb);
|
||||
if( iDb<0 ){
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
pBt = db->aDb[iDb].pBt;
|
||||
rc = sqlite3PagerWalInfo(sqlite3BtreePager(pBt), pnPrior, pnFrame);
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
#endif /* SQLITE_OMIT_WAL */
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
|
||||
/*
|
||||
** Given the name of a compile-time option, return true if that option
|
||||
|
||||
@@ -1159,6 +1159,10 @@ struct unixInodeInfo {
|
||||
sem_t *pSem; /* Named POSIX semaphore */
|
||||
char aSemName[MAX_PATHNAME+2]; /* Name of that semaphore */
|
||||
#endif
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
sqlite3_int64 nSharedMapping; /* Size of mapped region in bytes */
|
||||
void *pSharedMapping; /* Memory mapped region */
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -1311,6 +1315,13 @@ static void releaseInodeInfo(unixFile *pFile){
|
||||
pInode->nRef--;
|
||||
if( pInode->nRef==0 ){
|
||||
assert( pInode->pShmNode==0 );
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
if( pInode->pSharedMapping ){
|
||||
osMunmap(pInode->pSharedMapping, pInode->nSharedMapping);
|
||||
pInode->pSharedMapping = 0;
|
||||
pInode->nSharedMapping = 0;
|
||||
}
|
||||
#endif
|
||||
sqlite3_mutex_enter(pInode->pLockMutex);
|
||||
closePendingFds(pFile);
|
||||
sqlite3_mutex_leave(pInode->pLockMutex);
|
||||
@@ -2169,6 +2180,14 @@ static int nolockUnlock(sqlite3_file *NotUsed, int NotUsed2){
|
||||
** Close the file.
|
||||
*/
|
||||
static int nolockClose(sqlite3_file *id) {
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
unixFile *pFd = (unixFile*)id;
|
||||
if( pFd->pInode ){
|
||||
unixEnterMutex();
|
||||
releaseInodeInfo(pFd);
|
||||
unixLeaveMutex();
|
||||
}
|
||||
#endif
|
||||
return closeUnixFile(id);
|
||||
}
|
||||
|
||||
@@ -4000,6 +4019,9 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
|
||||
*(i64*)pArg = pFile->mmapSizeMax;
|
||||
if( newLimit>=0 && newLimit!=pFile->mmapSizeMax && pFile->nFetchOut==0 ){
|
||||
pFile->mmapSizeMax = newLimit;
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
if( pFile->pInode==0 )
|
||||
#endif
|
||||
if( pFile->mmapSize>0 ){
|
||||
unixUnmapfile(pFile);
|
||||
rc = unixMapfile(pFile, -1);
|
||||
@@ -4963,6 +4985,9 @@ static int unixShmUnmap(
|
||||
*/
|
||||
static void unixUnmapfile(unixFile *pFd){
|
||||
assert( pFd->nFetchOut==0 );
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
if( pFd->pInode ) return;
|
||||
#endif
|
||||
if( pFd->pMapRegion ){
|
||||
osMunmap(pFd->pMapRegion, pFd->mmapSizeActual);
|
||||
pFd->pMapRegion = 0;
|
||||
@@ -5094,6 +5119,28 @@ static int unixMapfile(unixFile *pFd, i64 nMap){
|
||||
nMap = pFd->mmapSizeMax;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
if( pFd->pInode ){
|
||||
unixInodeInfo *pInode = pFd->pInode;
|
||||
if( pFd->pMapRegion ) return SQLITE_OK;
|
||||
unixEnterMutex();
|
||||
if( pInode->pSharedMapping==0 ){
|
||||
u8 *pNew = osMmap(0, nMap, PROT_READ, MAP_SHARED, pFd->h, 0);
|
||||
if( pNew==MAP_FAILED ){
|
||||
unixLogError(SQLITE_OK, "mmap", pFd->zPath);
|
||||
pFd->mmapSizeMax = 0;
|
||||
}else{
|
||||
pInode->pSharedMapping = pNew;
|
||||
pInode->nSharedMapping = nMap;
|
||||
}
|
||||
}
|
||||
pFd->pMapRegion = pInode->pSharedMapping;
|
||||
pFd->mmapSizeActual = pFd->mmapSize = pInode->nSharedMapping;
|
||||
unixLeaveMutex();
|
||||
return SQLITE_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
assert( nMap>0 || (pFd->mmapSize==0 && pFd->pMapRegion==0) );
|
||||
if( nMap!=pFd->mmapSize ){
|
||||
unixRemapfile(pFd, nMap);
|
||||
@@ -5531,6 +5578,9 @@ static int fillInUnixFile(
|
||||
if( pLockingStyle == &posixIoMethods
|
||||
#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE
|
||||
|| pLockingStyle == &nfsIoMethods
|
||||
#endif
|
||||
#ifdef SQLITE_SHARED_MAPPING
|
||||
|| pLockingStyle == &nolockIoMethods
|
||||
#endif
|
||||
){
|
||||
unixEnterMutex();
|
||||
|
||||
+216
-49
@@ -670,6 +670,9 @@ struct Pager {
|
||||
u32 cksumInit; /* Quasi-random value added to every checksum */
|
||||
u32 nSubRec; /* Number of records written to sub-journal */
|
||||
Bitvec *pInJournal; /* One bit for each page in the database file */
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
Bitvec *pAllRead; /* Pages read within current CONCURRENT trans. */
|
||||
#endif
|
||||
sqlite3_file *fd; /* File descriptor for database */
|
||||
sqlite3_file *jfd; /* File descriptor for main journal */
|
||||
sqlite3_file *sjfd; /* File descriptor for sub-journal */
|
||||
@@ -811,20 +814,6 @@ static const unsigned char aJournalMagic[] = {
|
||||
*/
|
||||
#define PAGER_MAX_PGNO 2147483647
|
||||
|
||||
/*
|
||||
** The argument to this macro is a file descriptor (type sqlite3_file*).
|
||||
** Return 0 if it is not open, or non-zero (but not 1) if it is.
|
||||
**
|
||||
** This is so that expressions can be written as:
|
||||
**
|
||||
** if( isOpen(pPager->jfd) ){ ...
|
||||
**
|
||||
** instead of
|
||||
**
|
||||
** if( pPager->jfd->pMethods ){ ...
|
||||
*/
|
||||
#define isOpen(pFd) ((pFd)->pMethods!=0)
|
||||
|
||||
#ifdef SQLITE_DIRECT_OVERFLOW_READ
|
||||
/*
|
||||
** Return true if page pgno can be read directly from the database file
|
||||
@@ -937,7 +926,9 @@ static int assert_pager_state(Pager *p){
|
||||
if( !pagerUseWal(pPager) ){
|
||||
assert( p->eLock>=RESERVED_LOCK );
|
||||
}
|
||||
assert( pPager->dbSize==pPager->dbOrigSize );
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
assert( pPager->dbSize==pPager->dbOrigSize || pPager->pAllRead );
|
||||
#endif
|
||||
assert( pPager->dbOrigSize==pPager->dbFileSize );
|
||||
assert( pPager->dbOrigSize==pPager->dbHintSize );
|
||||
assert( pPager->setMaster==0 );
|
||||
@@ -956,6 +947,7 @@ static int assert_pager_state(Pager *p){
|
||||
assert( isOpen(p->jfd)
|
||||
|| p->journalMode==PAGER_JOURNALMODE_OFF
|
||||
|| p->journalMode==PAGER_JOURNALMODE_WAL
|
||||
|| p->journalMode==PAGER_JOURNALMODE_WAL2
|
||||
);
|
||||
}
|
||||
assert( pPager->dbOrigSize==pPager->dbFileSize );
|
||||
@@ -970,6 +962,7 @@ static int assert_pager_state(Pager *p){
|
||||
assert( isOpen(p->jfd)
|
||||
|| p->journalMode==PAGER_JOURNALMODE_OFF
|
||||
|| p->journalMode==PAGER_JOURNALMODE_WAL
|
||||
|| p->journalMode==PAGER_JOURNALMODE_WAL2
|
||||
|| (sqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC)
|
||||
);
|
||||
assert( pPager->dbOrigSize<=pPager->dbHintSize );
|
||||
@@ -982,6 +975,7 @@ static int assert_pager_state(Pager *p){
|
||||
assert( isOpen(p->jfd)
|
||||
|| p->journalMode==PAGER_JOURNALMODE_OFF
|
||||
|| p->journalMode==PAGER_JOURNALMODE_WAL
|
||||
|| p->journalMode==PAGER_JOURNALMODE_WAL2
|
||||
|| (sqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC)
|
||||
);
|
||||
break;
|
||||
@@ -1821,6 +1815,53 @@ static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/*
|
||||
** If they are not already, begin recording all pages read from the pager layer
|
||||
** by the b-tree layer This is used by concurrent transactions. Return
|
||||
** SQLITE_OK if successful, or an SQLite error code (SQLITE_NOMEM) if an error
|
||||
** occurs.
|
||||
*/
|
||||
int sqlite3PagerBeginConcurrent(Pager *pPager){
|
||||
int rc = SQLITE_OK;
|
||||
if( pPager->pAllRead==0 ){
|
||||
pPager->pAllRead = sqlite3BitvecCreate(pPager->dbSize);
|
||||
pPager->dbOrigSize = pPager->dbSize;
|
||||
if( pPager->pAllRead==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** Stop recording all pages read from the pager layer by the b-tree layer
|
||||
** and discard any current records.
|
||||
*/
|
||||
void sqlite3PagerEndConcurrent(Pager *pPager){
|
||||
sqlite3BitvecDestroy(pPager->pAllRead);
|
||||
pPager->pAllRead = 0;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** Return true if the database is in wal mode. False otherwise.
|
||||
*/
|
||||
int sqlite3PagerIsWal(Pager *pPager){
|
||||
return pPager->pWal!=0;
|
||||
}
|
||||
#endif /* SQLITE_OMIT_CONCURRENT */
|
||||
|
||||
/*
|
||||
** Free the Pager.pInJournal and Pager.pAllRead bitvec objects.
|
||||
*/
|
||||
static void pagerFreeBitvecs(Pager *pPager){
|
||||
sqlite3BitvecDestroy(pPager->pInJournal);
|
||||
pPager->pInJournal = 0;
|
||||
sqlite3PagerEndConcurrent(pPager);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if the pager is in exclusive mode and not
|
||||
** in the ERROR state. Otherwise, it switches the pager to PAGER_OPEN
|
||||
@@ -1845,8 +1886,7 @@ static void pager_unlock(Pager *pPager){
|
||||
|| pPager->eState==PAGER_ERROR
|
||||
);
|
||||
|
||||
sqlite3BitvecDestroy(pPager->pInJournal);
|
||||
pPager->pInJournal = 0;
|
||||
pagerFreeBitvecs(pPager);
|
||||
releaseAllSavepoints(pPager);
|
||||
|
||||
if( pagerUseWal(pPager) ){
|
||||
@@ -2080,7 +2120,7 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
|
||||
}
|
||||
pPager->journalOff = 0;
|
||||
}else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST
|
||||
|| (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL)
|
||||
|| (pPager->exclusiveMode && pPager->journalMode<PAGER_JOURNALMODE_WAL)
|
||||
){
|
||||
rc = zeroJournalHdr(pPager, hasMaster||pPager->tempFile);
|
||||
pPager->journalOff = 0;
|
||||
@@ -2094,7 +2134,8 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
|
||||
assert( sqlite3JournalIsInMemory(pPager->jfd)==0 );
|
||||
assert( pPager->journalMode==PAGER_JOURNALMODE_DELETE
|
||||
|| pPager->journalMode==PAGER_JOURNALMODE_MEMORY
|
||||
|| pPager->journalMode==PAGER_JOURNALMODE_WAL
|
||||
|| pPager->journalMode==PAGER_JOURNALMODE_WAL
|
||||
|| pPager->journalMode==PAGER_JOURNALMODE_WAL2
|
||||
);
|
||||
sqlite3OsClose(pPager->jfd);
|
||||
if( bDelete ){
|
||||
@@ -2114,8 +2155,7 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
|
||||
}
|
||||
#endif
|
||||
|
||||
sqlite3BitvecDestroy(pPager->pInJournal);
|
||||
pPager->pInJournal = 0;
|
||||
pagerFreeBitvecs(pPager);
|
||||
pPager->nRec = 0;
|
||||
if( rc==SQLITE_OK ){
|
||||
if( MEMDB || pagerFlushOnCommit(pPager, bCommit) ){
|
||||
@@ -3163,8 +3203,24 @@ static int pagerRollbackWal(Pager *pPager){
|
||||
** + Reload page content from the database (if refcount>0).
|
||||
*/
|
||||
pPager->dbSize = pPager->dbOrigSize;
|
||||
rc = sqlite3WalUndo(pPager->pWal, pagerUndoCallback, (void *)pPager);
|
||||
rc = sqlite3WalUndo(pPager->pWal, pagerUndoCallback, (void *)pPager,
|
||||
#ifdef SQLITE_OMIT_CONCURRENT
|
||||
0
|
||||
#else
|
||||
pPager->pAllRead!=0
|
||||
#endif
|
||||
);
|
||||
pList = sqlite3PcacheDirtyList(pPager->pPCache);
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/* If this is an CONCURRENT transaction, then page 1 must be reread from
|
||||
** the db file, even if it is not dirty. This is because the b-tree layer
|
||||
** may have already zeroed the nFree and iTrunk header fields. */
|
||||
if( rc==SQLITE_OK && (pList==0 || pList->pgno!=1) && pPager->pAllRead ){
|
||||
rc = pagerUndoCallback((void*)pPager, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
while( pList && rc==SQLITE_OK ){
|
||||
PgHdr *pNext = pList->pDirty;
|
||||
rc = pagerUndoCallback((void *)pPager, pList->pgno);
|
||||
@@ -3214,6 +3270,8 @@ static int pagerWalFrames(
|
||||
if( p->pgno<=nTruncate ){
|
||||
ppNext = &p->pDirty;
|
||||
nList++;
|
||||
PAGERTRACE(("TO-WAL %d page %d hash(%08x)\n",
|
||||
PAGERID(pPager), p->pgno, pager_pagehash(p)));
|
||||
}
|
||||
}
|
||||
assert( pList );
|
||||
@@ -3268,6 +3326,10 @@ static int pagerBeginReadTransaction(Pager *pPager){
|
||||
if( rc!=SQLITE_OK || changed ){
|
||||
pager_reset(pPager);
|
||||
if( USEFETCH(pPager) ) sqlite3OsUnfetch(pPager->fd, 0, 0);
|
||||
assert( pPager->journalMode==PAGER_JOURNALMODE_WAL
|
||||
|| pPager->journalMode==PAGER_JOURNALMODE_WAL2
|
||||
);
|
||||
pPager->journalMode = sqlite3WalJournalMode(pPager->pWal);
|
||||
}
|
||||
|
||||
return rc;
|
||||
@@ -3363,9 +3425,9 @@ static int pagerOpenWalIfPresent(Pager *pPager){
|
||||
rc = sqlite3OsDelete(pPager->pVfs, pPager->zWal, 0);
|
||||
}else{
|
||||
testcase( sqlite3PcachePagecount(pPager->pPCache)==0 );
|
||||
rc = sqlite3PagerOpenWal(pPager, 0);
|
||||
rc = sqlite3PagerOpenWal(pPager, 0, 0);
|
||||
}
|
||||
}else if( pPager->journalMode==PAGER_JOURNALMODE_WAL ){
|
||||
}else if( pPager->journalMode>=PAGER_JOURNALMODE_WAL ){
|
||||
pPager->journalMode = PAGER_JOURNALMODE_DELETE;
|
||||
}
|
||||
}
|
||||
@@ -4274,7 +4336,7 @@ static int syncJournal(Pager *pPager, int newHdr){
|
||||
assert( assert_pager_state(pPager) );
|
||||
assert( !pagerUseWal(pPager) );
|
||||
|
||||
rc = sqlite3PagerExclusiveLock(pPager);
|
||||
rc = sqlite3PagerExclusiveLock(pPager, 0, 0);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
if( !pPager->noSync ){
|
||||
@@ -4632,6 +4694,12 @@ static int pagerStress(void *p, PgHdr *pPg){
|
||||
pPager->aStat[PAGER_STAT_SPILL]++;
|
||||
pPg->pDirty = 0;
|
||||
if( pagerUseWal(pPager) ){
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/* If the transaction is a "BEGIN CONCURRENT" transaction, the page
|
||||
** cannot be flushed to disk. Return early in this case. */
|
||||
if( pPager->pAllRead ) return SQLITE_OK;
|
||||
#endif
|
||||
|
||||
/* Write a single frame for this page to the log. */
|
||||
rc = subjournalPageIfRequired(pPg);
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -5465,6 +5533,18 @@ static int getPageNormal(
|
||||
assert( assert_pager_state(pPager) );
|
||||
assert( pPager->hasHeldSharedLock==1 );
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/* If this is an CONCURRENT transaction and the page being read was
|
||||
** present in the database file when the transaction was opened,
|
||||
** mark it as read in the pAllRead vector. */
|
||||
pPg = 0;
|
||||
if( pPager->pAllRead && pgno<=pPager->dbOrigSize ){
|
||||
PAGERTRACE(("USING page %d\n", pgno));
|
||||
rc = sqlite3BitvecSet(pPager->pAllRead, pgno);
|
||||
if( rc!=SQLITE_OK ) goto pager_acquire_err;
|
||||
}
|
||||
#endif
|
||||
|
||||
if( pgno==0 ) return SQLITE_CORRUPT_BKPT;
|
||||
pBase = sqlite3PcacheFetch(pPager->pPCache, pgno, 3);
|
||||
if( pBase==0 ){
|
||||
@@ -5806,11 +5886,14 @@ static int pager_open_journal(Pager *pPager){
|
||||
** Begin a write-transaction on the specified pager object. If a
|
||||
** write-transaction has already been opened, this function is a no-op.
|
||||
**
|
||||
** If the exFlag argument is false, then acquire at least a RESERVED
|
||||
** lock on the database file. If exFlag is true, then acquire at least
|
||||
** If the exFlag argument is 0, then acquire at least a RESERVED
|
||||
** lock on the database file. If exFlag is >0, then acquire at least
|
||||
** an EXCLUSIVE lock. If such a lock is already held, no locking
|
||||
** functions need be called.
|
||||
**
|
||||
** If (exFlag<0) and the database is in WAL mode, do not take any locks.
|
||||
** The transaction will run in CONCURRENT mode instead.
|
||||
**
|
||||
** If the subjInMemory argument is non-zero, then any sub-journal opened
|
||||
** within this transaction will be opened as an in-memory file. This
|
||||
** has no effect if the sub-journal is already opened (as it may be when
|
||||
@@ -5828,7 +5911,6 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
|
||||
|
||||
if( ALWAYS(pPager->eState==PAGER_READER) ){
|
||||
assert( pPager->pInJournal==0 );
|
||||
|
||||
if( pagerUseWal(pPager) ){
|
||||
/* If the pager is configured to use locking_mode=exclusive, and an
|
||||
** exclusive lock on the database is not already held, obtain it now.
|
||||
@@ -5844,9 +5926,10 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
|
||||
/* Grab the write lock on the log file. If successful, upgrade to
|
||||
** PAGER_RESERVED state. Otherwise, return an error code to the caller.
|
||||
** The busy-handler is not invoked if another connection already
|
||||
** holds the write-lock. If possible, the upper layer will call it.
|
||||
*/
|
||||
rc = sqlite3WalBeginWriteTransaction(pPager->pWal);
|
||||
** holds the write-lock. If possible, the upper layer will call it. */
|
||||
if( exFlag>=0 ){
|
||||
rc = sqlite3WalBeginWriteTransaction(pPager->pWal);
|
||||
}
|
||||
}else{
|
||||
/* Obtain a RESERVED lock on the database file. If the exFlag parameter
|
||||
** is true, then immediately upgrade this to an EXCLUSIVE lock. The
|
||||
@@ -5854,7 +5937,7 @@ int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
|
||||
** lock, but not when obtaining the RESERVED lock.
|
||||
*/
|
||||
rc = pagerLockDb(pPager, RESERVED_LOCK);
|
||||
if( rc==SQLITE_OK && exFlag ){
|
||||
if( rc==SQLITE_OK && exFlag>0 ){
|
||||
rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
|
||||
}
|
||||
}
|
||||
@@ -6154,7 +6237,7 @@ int sqlite3PagerWrite(PgHdr *pPg){
|
||||
** to sqlite3PagerWrite(). In other words, return TRUE if it is ok
|
||||
** to change the content of the page.
|
||||
*/
|
||||
#ifndef NDEBUG
|
||||
#if !defined(SQLITE_OMIT_CONCURRENT) || !defined(NDEBUG)
|
||||
int sqlite3PagerIswriteable(DbPage *pPg){
|
||||
return pPg->flags & PGHDR_WRITEABLE;
|
||||
}
|
||||
@@ -6310,17 +6393,26 @@ int sqlite3PagerSync(Pager *pPager, const char *zMaster){
|
||||
}
|
||||
|
||||
/*
|
||||
** This function may only be called while a write-transaction is active in
|
||||
** rollback. If the connection is in WAL mode, this call is a no-op.
|
||||
** Otherwise, if the connection does not already have an EXCLUSIVE lock on
|
||||
** the database file, an attempt is made to obtain one.
|
||||
** This function is called to ensure that all locks required to commit the
|
||||
** current write-transaction to the database file are held. If the db is
|
||||
** in rollback mode, this means the EXCLUSIVE lock on the database file.
|
||||
**
|
||||
** If the EXCLUSIVE lock is already held or the attempt to obtain it is
|
||||
** successful, or the connection is in WAL mode, SQLITE_OK is returned.
|
||||
** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is
|
||||
** returned.
|
||||
** Or, if this is a non-CONCURRENT transaction on a wal-mode database, this
|
||||
** function is a no-op.
|
||||
**
|
||||
** If this is an CONCURRENT transaction on a wal-mode database, this function
|
||||
** attempts to obtain the WRITER lock on the wal file and also checks to
|
||||
** see that the transaction can be safely committed (does not commit with
|
||||
** any other transaction committed since it was opened).
|
||||
**
|
||||
** If the required locks are already held or successfully obtained and
|
||||
** the transaction can be committed, SQLITE_OK is returned. If a required lock
|
||||
** cannot be obtained, SQLITE_BUSY is returned. Or, if the current transaction
|
||||
** is CONCURRENT and cannot be committed due to a conflict, SQLITE_BUSY_SNAPSHOT
|
||||
** is returned. Otherwise, if some other error occurs (IO error, OOM etc.),
|
||||
** and SQLite error code is returned.
|
||||
*/
|
||||
int sqlite3PagerExclusiveLock(Pager *pPager){
|
||||
int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1, Pgno *piConflict){
|
||||
int rc = pPager->errCode;
|
||||
assert( assert_pager_state(pPager) );
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -6332,10 +6424,73 @@ int sqlite3PagerExclusiveLock(Pager *pPager){
|
||||
if( 0==pagerUseWal(pPager) ){
|
||||
rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
|
||||
}
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
else{
|
||||
if( pPager->pAllRead ){
|
||||
/* This is an CONCURRENT transaction. Attempt to lock the wal database
|
||||
** here. If SQLITE_BUSY (but not SQLITE_BUSY_SNAPSHOT) is returned,
|
||||
** invoke the busy-handler and try again for as long as it returns
|
||||
** non-zero. */
|
||||
do {
|
||||
rc = sqlite3WalLockForCommit(
|
||||
pPager->pWal, pPage1, pPager->pAllRead, piConflict
|
||||
);
|
||||
}while( rc==SQLITE_BUSY
|
||||
&& pPager->xBusyHandler(pPager->pBusyHandlerArg)
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_OMIT_CONCURRENT */
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/*
|
||||
** This function is called as part of committing an CONCURRENT transaction.
|
||||
** At this point the wal WRITER lock is held, and all pages in the cache
|
||||
** except for page 1 are compatible with the snapshot at the head of the
|
||||
** wal file.
|
||||
**
|
||||
** This function updates the in-memory data structures and reloads the
|
||||
** contents of page 1 so that the client is operating on the snapshot
|
||||
** at the head of the wal file.
|
||||
**
|
||||
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
|
||||
*/
|
||||
int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage *pPage1){
|
||||
int rc;
|
||||
|
||||
assert( pPager->pWal && pPager->pAllRead );
|
||||
rc = sqlite3WalUpgradeSnapshot(pPager->pWal);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = readDbPage(pPage1);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** Set the in-memory cache of the database file size to nSz pages.
|
||||
*/
|
||||
void sqlite3PagerSetDbsize(Pager *pPager, Pgno nSz){
|
||||
pPager->dbSize = nSz;
|
||||
}
|
||||
|
||||
/* !defined(SQLITE_OMIT_CONCURRENT)
|
||||
**
|
||||
** If this is a WAL mode connection and the WRITER lock is currently held,
|
||||
** relinquish it.
|
||||
*/
|
||||
void sqlite3PagerDropExclusiveLock(Pager *pPager){
|
||||
if( pagerUseWal(pPager) ){
|
||||
sqlite3WalEndWriteTransaction(pPager->pWal);
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_OMIT_CONCURRENT */
|
||||
|
||||
|
||||
/*
|
||||
** Sync the database file for the pager pPager. zMaster points to the name
|
||||
** of a master journal file that should be written into the individual
|
||||
@@ -7303,6 +7458,7 @@ int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){
|
||||
|| eMode==PAGER_JOURNALMODE_PERSIST
|
||||
|| eMode==PAGER_JOURNALMODE_OFF
|
||||
|| eMode==PAGER_JOURNALMODE_WAL
|
||||
|| eMode==PAGER_JOURNALMODE_WAL2
|
||||
|| eMode==PAGER_JOURNALMODE_MEMORY );
|
||||
|
||||
/* This routine is only called from the OP_JournalMode opcode, and
|
||||
@@ -7337,9 +7493,12 @@ int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){
|
||||
assert( (PAGER_JOURNALMODE_MEMORY & 5)==4 );
|
||||
assert( (PAGER_JOURNALMODE_OFF & 5)==0 );
|
||||
assert( (PAGER_JOURNALMODE_WAL & 5)==5 );
|
||||
assert( (PAGER_JOURNALMODE_WAL2 & 5)==4 );
|
||||
|
||||
assert( isOpen(pPager->fd) || pPager->exclusiveMode );
|
||||
if( !pPager->exclusiveMode && (eOld & 5)==1 && (eMode & 1)==0 ){
|
||||
if( !pPager->exclusiveMode && (eOld & 5)==1 && (eMode & 1)==0
|
||||
&& eMode!=PAGER_JOURNALMODE_WAL2 /* TODO: fix this if possible */
|
||||
){
|
||||
|
||||
/* In this case we would like to delete the journal file. If it is
|
||||
** not possible, then that is not a problem. Deleting the journal file
|
||||
@@ -7502,7 +7661,7 @@ static int pagerExclusiveLock(Pager *pPager){
|
||||
** lock on the database file and use heap-memory to store the wal-index
|
||||
** in. Otherwise, use the normal shared-memory.
|
||||
*/
|
||||
static int pagerOpenWal(Pager *pPager){
|
||||
static int pagerOpenWal(Pager *pPager, int bWal2){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
assert( pPager->pWal==0 && pPager->tempFile==0 );
|
||||
@@ -7523,7 +7682,7 @@ static int pagerOpenWal(Pager *pPager){
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3WalOpen(pPager->pVfs,
|
||||
pPager->fd, pPager->zWal, pPager->exclusiveMode,
|
||||
pPager->journalSizeLimit, &pPager->pWal
|
||||
pPager->journalSizeLimit, bWal2, &pPager->pWal
|
||||
);
|
||||
}
|
||||
pagerFixMaplimit(pPager);
|
||||
@@ -7549,6 +7708,7 @@ static int pagerOpenWal(Pager *pPager){
|
||||
*/
|
||||
int sqlite3PagerOpenWal(
|
||||
Pager *pPager, /* Pager object */
|
||||
int bWal2, /* Open in wal2 mode if not already open */
|
||||
int *pbOpen /* OUT: Set to true if call is a no-op */
|
||||
){
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
@@ -7565,9 +7725,9 @@ int sqlite3PagerOpenWal(
|
||||
/* Close any rollback journal previously open */
|
||||
sqlite3OsClose(pPager->jfd);
|
||||
|
||||
rc = pagerOpenWal(pPager);
|
||||
rc = pagerOpenWal(pPager, bWal2);
|
||||
if( rc==SQLITE_OK ){
|
||||
pPager->journalMode = PAGER_JOURNALMODE_WAL;
|
||||
pPager->journalMode = bWal2?PAGER_JOURNALMODE_WAL2:PAGER_JOURNALMODE_WAL;
|
||||
pPager->eState = PAGER_OPEN;
|
||||
}
|
||||
}else{
|
||||
@@ -7589,7 +7749,9 @@ int sqlite3PagerOpenWal(
|
||||
int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
assert( pPager->journalMode==PAGER_JOURNALMODE_WAL );
|
||||
assert( pPager->journalMode==PAGER_JOURNALMODE_WAL
|
||||
|| pPager->journalMode==PAGER_JOURNALMODE_WAL2
|
||||
);
|
||||
|
||||
/* If the log file is not already open, but does exist in the file-system,
|
||||
** it may need to be checkpointed before the connection can switch to
|
||||
@@ -7604,7 +7766,7 @@ int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){
|
||||
);
|
||||
}
|
||||
if( rc==SQLITE_OK && logexists ){
|
||||
rc = pagerOpenWal(pPager);
|
||||
rc = pagerOpenWal(pPager, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7698,6 +7860,11 @@ void sqlite3PagerSnapshotUnlock(Pager *pPager){
|
||||
}
|
||||
|
||||
#endif /* SQLITE_ENABLE_SNAPSHOT */
|
||||
|
||||
int sqlite3PagerWalInfo(Pager *pPager, u32 *pnPrior, u32 *pnFrame){
|
||||
return sqlite3WalInfo(pPager->pWal, pnPrior, pnFrame);
|
||||
}
|
||||
|
||||
#endif /* !SQLITE_OMIT_WAL */
|
||||
|
||||
#ifdef SQLITE_ENABLE_ZIPVFS
|
||||
|
||||
+37
-3
@@ -81,6 +81,23 @@ typedef struct PgHdr DbPage;
|
||||
#define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */
|
||||
#define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */
|
||||
#define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */
|
||||
#define PAGER_JOURNALMODE_WAL2 6 /* Use write-ahead logging mode 2 */
|
||||
|
||||
#define isWalMode(x) ((x)==PAGER_JOURNALMODE_WAL || (x)==PAGER_JOURNALMODE_WAL2)
|
||||
|
||||
/*
|
||||
** The argument to this macro is a file descriptor (type sqlite3_file*).
|
||||
** Return 0 if it is not open, or non-zero (but not 1) if it is.
|
||||
**
|
||||
** This is so that expressions can be written as:
|
||||
**
|
||||
** if( isOpen(pPager->jfd) ){ ...
|
||||
**
|
||||
** instead of
|
||||
**
|
||||
** if( pPager->jfd->pMethods ){ ...
|
||||
*/
|
||||
#define isOpen(pFd) ((pFd)->pMethods!=0)
|
||||
|
||||
/*
|
||||
** Flags that make up the mask passed to sqlite3PagerGet().
|
||||
@@ -165,7 +182,7 @@ void *sqlite3PagerGetExtra(DbPage *);
|
||||
void sqlite3PagerPagecount(Pager*, int*);
|
||||
int sqlite3PagerBegin(Pager*, int exFlag, int);
|
||||
int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int);
|
||||
int sqlite3PagerExclusiveLock(Pager*);
|
||||
int sqlite3PagerExclusiveLock(Pager*, DbPage *pPage1, Pgno*);
|
||||
int sqlite3PagerSync(Pager *pPager, const char *zMaster);
|
||||
int sqlite3PagerCommitPhaseTwo(Pager*);
|
||||
int sqlite3PagerRollback(Pager*);
|
||||
@@ -173,11 +190,12 @@ int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
|
||||
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
|
||||
int sqlite3PagerSharedLock(Pager *pPager);
|
||||
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
int sqlite3PagerCheckpoint(Pager *pPager, sqlite3*, int, int*, int*);
|
||||
int sqlite3PagerWalSupported(Pager *pPager);
|
||||
int sqlite3PagerWalCallback(Pager *pPager);
|
||||
int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen);
|
||||
int sqlite3PagerOpenWal(Pager *pPager, int, int *pisOpen);
|
||||
int sqlite3PagerCloseWal(Pager *pPager, sqlite3*);
|
||||
# ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
|
||||
@@ -224,6 +242,23 @@ void sqlite3PagerTruncateImage(Pager*,Pgno);
|
||||
|
||||
void sqlite3PagerRekey(DbPage*, Pgno, u16);
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
void sqlite3PagerEndConcurrent(Pager*);
|
||||
int sqlite3PagerBeginConcurrent(Pager*);
|
||||
void sqlite3PagerDropExclusiveLock(Pager*);
|
||||
int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage*);
|
||||
void sqlite3PagerSetDbsize(Pager *pPager, Pgno);
|
||||
int sqlite3PagerIsWal(Pager*);
|
||||
#else
|
||||
# define sqlite3PagerEndConcurrent(x)
|
||||
#endif
|
||||
|
||||
#if defined(SQLITE_DEBUG) || !defined(SQLITE_OMIT_CONCURRENT)
|
||||
int sqlite3PagerIswriteable(DbPage*);
|
||||
#endif
|
||||
|
||||
int sqlite3PagerWalInfo(Pager*, u32 *pnPrior, u32 *pnFrame);
|
||||
|
||||
#if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL)
|
||||
void *sqlite3PagerCodec(DbPage *);
|
||||
#endif
|
||||
@@ -231,7 +266,6 @@ void *sqlite3PagerCodec(DbPage *);
|
||||
/* Functions to support testing and debugging. */
|
||||
#if !defined(NDEBUG) || defined(SQLITE_TEST)
|
||||
Pgno sqlite3PagerPagenumber(DbPage*);
|
||||
int sqlite3PagerIswriteable(DbPage*);
|
||||
#endif
|
||||
#ifdef SQLITE_TEST
|
||||
int *sqlite3PagerStats(Pager*);
|
||||
|
||||
+18
-3
@@ -99,6 +99,13 @@
|
||||
*/
|
||||
struct TrigEvent { int a; IdList * b; };
|
||||
|
||||
/*
|
||||
** Generate a syntax error
|
||||
*/
|
||||
static void parserSyntaxError(Parse *pParse, Token *p){
|
||||
sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", p);
|
||||
}
|
||||
|
||||
struct FrameBound { int eType; Expr *pExpr; };
|
||||
|
||||
/*
|
||||
@@ -136,7 +143,16 @@ trans_opt ::= TRANSACTION nm.
|
||||
transtype(A) ::= . {A = TK_DEFERRED;}
|
||||
transtype(A) ::= DEFERRED(X). {A = @X; /*A-overwrites-X*/}
|
||||
transtype(A) ::= IMMEDIATE(X). {A = @X; /*A-overwrites-X*/}
|
||||
transtype(A) ::= EXCLUSIVE(X). {A = @X; /*A-overwrites-X*/}
|
||||
transtype(A) ::= ID(X). {
|
||||
Token *p = &X;
|
||||
if( p->n==9 && sqlite3_strnicmp(p->z,"exclusive",9)==0 ){
|
||||
A = TK_EXCLUSIVE;
|
||||
}else if( p->n==10 && sqlite3_strnicmp(p->z,"concurrent",10)==0 ){
|
||||
A = TK_CONCURRENT; /*A-overwrites-X*/
|
||||
}else{
|
||||
parserSyntaxError(pParse, p);
|
||||
}
|
||||
}
|
||||
cmd ::= COMMIT|END(X) trans_opt. {sqlite3EndTransaction(pParse,@X);}
|
||||
cmd ::= ROLLBACK(X) trans_opt. {sqlite3EndTransaction(pParse,@X);}
|
||||
|
||||
@@ -253,7 +269,6 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);}
|
||||
//
|
||||
%token_class id ID|INDEXED.
|
||||
|
||||
|
||||
// And "ids" is an identifer-or-string.
|
||||
//
|
||||
%token_class ids ID|STRING.
|
||||
@@ -1011,7 +1026,7 @@ expr(A) ::= VARIABLE(X). {
|
||||
Token t = X; /*A-overwrites-X*/
|
||||
assert( t.n>=2 );
|
||||
if( pParse->nested==0 ){
|
||||
sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t);
|
||||
parserSyntaxError(pParse, &t);
|
||||
A = 0;
|
||||
}else{
|
||||
A = sqlite3PExpr(pParse, TK_REGISTER, 0, 0);
|
||||
|
||||
+2
-1
@@ -260,7 +260,7 @@ const char *sqlite3JournalModename(int eMode){
|
||||
static char * const azModeName[] = {
|
||||
"delete", "persist", "off", "truncate", "memory"
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
, "wal"
|
||||
, "wal", "wal2"
|
||||
#endif
|
||||
};
|
||||
assert( PAGER_JOURNALMODE_DELETE==0 );
|
||||
@@ -269,6 +269,7 @@ const char *sqlite3JournalModename(int eMode){
|
||||
assert( PAGER_JOURNALMODE_TRUNCATE==3 );
|
||||
assert( PAGER_JOURNALMODE_MEMORY==4 );
|
||||
assert( PAGER_JOURNALMODE_WAL==5 );
|
||||
assert( PAGER_JOURNALMODE_WAL2==6 );
|
||||
assert( eMode>=0 && eMode<=ArraySize(azModeName) );
|
||||
|
||||
if( eMode==ArraySize(azModeName) ) return 0;
|
||||
|
||||
@@ -9197,6 +9197,31 @@ SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
|
||||
*/
|
||||
SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Wal related information regarding the most recent COMMIT
|
||||
** EXPERIMENTAL
|
||||
**
|
||||
** This function reports on the state of the wal file (if any) for database
|
||||
** zDb, which should be "main", "temp", or the name of the attached database.
|
||||
** Its results - the values written to the output parameters - are only
|
||||
** defined if the most recent SQL command on the connection was a successful
|
||||
** COMMIT that wrote data to wal-mode database zDb.
|
||||
**
|
||||
** Assuming the above conditions are met, output parameter (*pnFrame) is set
|
||||
** to the total number of frames in the wal file. Parameter (*pnPrior) is
|
||||
** set to the number of frames that were present in the wal file before the
|
||||
** most recent transaction was committed. So that the number of frames written
|
||||
** by the most recent transaction is (*pnFrame)-(*pnPrior).
|
||||
**
|
||||
** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code. It
|
||||
** is not an error if this function is called at a time when the results
|
||||
** are undefined.
|
||||
*/
|
||||
SQLITE_EXPERIMENTAL int sqlite3_wal_info(
|
||||
sqlite3 *db, const char *zDb,
|
||||
unsigned int *pnPrior, unsigned int *pnFrame
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Serialize a database
|
||||
**
|
||||
|
||||
@@ -1387,6 +1387,7 @@ struct sqlite3 {
|
||||
u16 dbOptFlags; /* Flags to enable/disable optimizations */
|
||||
u8 enc; /* Text encoding */
|
||||
u8 autoCommit; /* The auto-commit flag. */
|
||||
u8 bConcurrent; /* Current transaction is "CONCURRENT" */
|
||||
u8 temp_store; /* 1: file 2: memory 0: default */
|
||||
u8 mallocFailed; /* True if we have seen a malloc failure */
|
||||
u8 bBenignMalloc; /* Do not require OOMs if true */
|
||||
|
||||
+38
-2
@@ -7612,6 +7612,41 @@ static int SQLITE_TCLAPI test_dbconfig_maindbname_icecube(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Usage: sqlite3_wal_info DB DBNAME
|
||||
*/
|
||||
static int SQLITE_TCLAPI test_wal_info(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
int rc;
|
||||
sqlite3 *db;
|
||||
char *zName;
|
||||
unsigned int nPrior;
|
||||
unsigned int nFrame;
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
zName = Tcl_GetString(objv[2]);
|
||||
|
||||
rc = sqlite3_wal_info(db, zName, &nPrior, &nFrame);
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
return TCL_ERROR;
|
||||
}else{
|
||||
Tcl_Obj *pNew = Tcl_NewObj();
|
||||
Tcl_ListObjAppendElement(interp, pNew, Tcl_NewWideIntObj((i64)nPrior));
|
||||
Tcl_ListObjAppendElement(interp, pNew, Tcl_NewWideIntObj((i64)nFrame));
|
||||
Tcl_SetObjResult(interp, pNew);
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Usage: sqlite3_mmap_warm DB DBNAME
|
||||
*/
|
||||
@@ -7989,8 +8024,9 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "sqlite3_snapshot_open_blob", test_snapshot_open_blob, 0 },
|
||||
{ "sqlite3_snapshot_cmp_blob", test_snapshot_cmp_blob, 0 },
|
||||
#endif
|
||||
{ "sqlite3_delete_database", test_delete_database, 0 },
|
||||
{ "atomic_batch_write", test_atomic_batch_write, 0 },
|
||||
{ "sqlite3_delete_database", test_delete_database, 0 },
|
||||
{ "sqlite3_wal_info", test_wal_info, 0 },
|
||||
{ "atomic_batch_write", test_atomic_batch_write, 0 },
|
||||
{ "sqlite3_mmap_warm", test_mmap_warm, 0 },
|
||||
{ "sqlite3_config_sorterref", test_config_sorterref, 0 },
|
||||
{ "decode_hexdb", test_decode_hexdb, 0 },
|
||||
|
||||
@@ -672,6 +672,12 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY);
|
||||
Tcl_SetVar2(interp, "sqlite_options", "truncate_opt", "1", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
Tcl_SetVar2(interp, "sqlite_options", "concurrent", "1", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
Tcl_SetVar2(interp, "sqlite_options", "concurrent", "0", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_OMIT_UTF16
|
||||
Tcl_SetVar2(interp, "sqlite_options", "utf16", "0", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
|
||||
+17
-5
@@ -190,7 +190,7 @@ static int SQLITE_TCLAPI hexio_write(
|
||||
}
|
||||
|
||||
/*
|
||||
** USAGE: hexio_get_int HEXDATA
|
||||
** USAGE: hexio_get_int [-littleendian] HEXDATA
|
||||
**
|
||||
** Interpret the HEXDATA argument as a big-endian integer. Return
|
||||
** the value of that integer. HEXDATA can contain between 2 and 8
|
||||
@@ -207,12 +207,20 @@ static int SQLITE_TCLAPI hexio_get_int(
|
||||
const unsigned char *zIn;
|
||||
unsigned char *aOut;
|
||||
unsigned char aNum[4];
|
||||
int bLittle = 0;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "HEXDATA");
|
||||
if( objc==3 ){
|
||||
int n;
|
||||
char *z = Tcl_GetStringFromObj(objv[1], &n);
|
||||
if( n>=2 && n<=13 && memcmp(z, "-littleendian", n)==0 ){
|
||||
bLittle = 1;
|
||||
}
|
||||
}
|
||||
if( (objc-bLittle)!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "[-littleendian] HEXDATA");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
zIn = (const unsigned char *)Tcl_GetStringFromObj(objv[1], &nIn);
|
||||
zIn = (const unsigned char *)Tcl_GetStringFromObj(objv[1+bLittle], &nIn);
|
||||
aOut = sqlite3_malloc( nIn/2 );
|
||||
if( aOut==0 ){
|
||||
return TCL_ERROR;
|
||||
@@ -225,7 +233,11 @@ static int SQLITE_TCLAPI hexio_get_int(
|
||||
memcpy(&aNum[4-nOut], aOut, nOut);
|
||||
}
|
||||
sqlite3_free(aOut);
|
||||
val = (aNum[0]<<24) | (aNum[1]<<16) | (aNum[2]<<8) | aNum[3];
|
||||
if( bLittle ){
|
||||
val = (aNum[3]<<24) | (aNum[2]<<16) | (aNum[1]<<8) | aNum[0];
|
||||
}else{
|
||||
val = (aNum[0]<<24) | (aNum[1]<<16) | (aNum[2]<<8) | aNum[3];
|
||||
}
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(val));
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){
|
||||
extern int Fts5tcl_Init(Tcl_Interp *);
|
||||
extern int SqliteRbu_Init(Tcl_Interp*);
|
||||
extern int Sqlitetesttcl_Init(Tcl_Interp*);
|
||||
extern int Bgckpt_Init(Tcl_Interp*);
|
||||
#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
|
||||
extern int Sqlitetestfts3_Init(Tcl_Interp *interp);
|
||||
#endif
|
||||
@@ -165,6 +166,8 @@ const char *sqlite3TestInit(Tcl_Interp *interp){
|
||||
Fts5tcl_Init(interp);
|
||||
SqliteRbu_Init(interp);
|
||||
Sqlitetesttcl_Init(interp);
|
||||
Bgckpt_Init(interp);
|
||||
|
||||
|
||||
#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
|
||||
Sqlitetestfts3_Init(interp);
|
||||
|
||||
@@ -391,6 +391,7 @@ end_of_vacuum:
|
||||
** is closed by the DETACH.
|
||||
*/
|
||||
db->autoCommit = 1;
|
||||
assert( db->bConcurrent==0 );
|
||||
|
||||
if( pDb ){
|
||||
sqlite3BtreeClose(pDb->pBt);
|
||||
|
||||
+83
-32
@@ -3074,6 +3074,7 @@ case OP_Savepoint: {
|
||||
** is committed.
|
||||
*/
|
||||
int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint;
|
||||
assert( db->bConcurrent==0 || db->isTransactionSavepoint==0 );
|
||||
if( isTransaction && p1==SAVEPOINT_RELEASE ){
|
||||
if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
|
||||
goto vdbe_return;
|
||||
@@ -3150,23 +3151,31 @@ case OP_Savepoint: {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: AutoCommit P1 P2 * * *
|
||||
/* Opcode: AutoCommit P1 P2 P3 * *
|
||||
**
|
||||
** Set the database auto-commit flag to P1 (1 or 0). If P2 is true, roll
|
||||
** back any currently active btree transactions. If there are any active
|
||||
** VMs (apart from this one), then a ROLLBACK fails. A COMMIT fails if
|
||||
** there are active writing VMs or active VMs that use shared cache.
|
||||
**
|
||||
** If P3 is non-zero, then this instruction is being executed as part of
|
||||
** a "BEGIN CONCURRENT" command.
|
||||
**
|
||||
** This instruction causes the VM to halt.
|
||||
*/
|
||||
case OP_AutoCommit: {
|
||||
int desiredAutoCommit;
|
||||
int iRollback;
|
||||
int bConcurrent;
|
||||
int hrc;
|
||||
|
||||
desiredAutoCommit = pOp->p1;
|
||||
iRollback = pOp->p2;
|
||||
bConcurrent = pOp->p3;
|
||||
assert( desiredAutoCommit==1 || desiredAutoCommit==0 );
|
||||
assert( desiredAutoCommit==1 || iRollback==0 );
|
||||
assert( desiredAutoCommit==0 || bConcurrent==0 );
|
||||
assert( db->autoCommit==0 || db->bConcurrent==0 );
|
||||
assert( db->nVdbeActive>0 ); /* At least this one VM is active */
|
||||
assert( p->bIsReader );
|
||||
|
||||
@@ -3175,10 +3184,17 @@ case OP_AutoCommit: {
|
||||
assert( desiredAutoCommit==1 );
|
||||
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
|
||||
db->autoCommit = 1;
|
||||
}else if( desiredAutoCommit && db->nVdbeWrite>0 ){
|
||||
/* If this instruction implements a COMMIT and other VMs are writing
|
||||
** return an error indicating that the other VMs must complete first.
|
||||
*/
|
||||
db->bConcurrent = 0;
|
||||
}else if( desiredAutoCommit
|
||||
&& (db->nVdbeWrite>0 || (db->bConcurrent && db->nVdbeActive>1)) ){
|
||||
/* A transaction may only be committed if there are no other active
|
||||
** writer VMs. If the transaction is CONCURRENT, then it may only be
|
||||
** committed if there are no active VMs at all (readers or writers).
|
||||
**
|
||||
** If this instruction is a COMMIT and the transaction may not be
|
||||
** committed due to one of the conditions above, return an error
|
||||
** indicating that other VMs must complete before the COMMIT can
|
||||
** be processed. */
|
||||
sqlite3VdbeError(p, "cannot commit transaction - "
|
||||
"SQL statements in progress");
|
||||
rc = SQLITE_BUSY;
|
||||
@@ -3188,12 +3204,15 @@ case OP_AutoCommit: {
|
||||
}else{
|
||||
db->autoCommit = (u8)desiredAutoCommit;
|
||||
}
|
||||
if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){
|
||||
hrc = sqlite3VdbeHalt(p);
|
||||
if( (hrc & 0xFF)==SQLITE_BUSY ){
|
||||
p->pc = (int)(pOp - aOp);
|
||||
db->autoCommit = (u8)(1-desiredAutoCommit);
|
||||
p->rc = rc = SQLITE_BUSY;
|
||||
p->rc = hrc;
|
||||
rc = SQLITE_BUSY;
|
||||
goto vdbe_return;
|
||||
}
|
||||
db->bConcurrent = (u8)bConcurrent;
|
||||
assert( db->nStatement==0 );
|
||||
sqlite3CloseSavepoints(db);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
@@ -3384,10 +3403,22 @@ case OP_SetCookie: {
|
||||
pDb = &db->aDb[pOp->p1];
|
||||
assert( pDb->pBt!=0 );
|
||||
assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) );
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
if( db->bConcurrent
|
||||
&& (pOp->p2==BTREE_USER_VERSION || pOp->p2==BTREE_APPLICATION_ID)
|
||||
){
|
||||
rc = SQLITE_ERROR;
|
||||
sqlite3VdbeError(p, "cannot modify %s within CONCURRENT transaction",
|
||||
pOp->p2==BTREE_USER_VERSION ? "user_version" : "application_id"
|
||||
);
|
||||
goto abort_due_to_error;
|
||||
}
|
||||
#endif
|
||||
/* See note about index shifting on OP_ReadCookie */
|
||||
rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, pOp->p3);
|
||||
if( pOp->p2==BTREE_SCHEMA_VERSION ){
|
||||
/* When the schema cookie changes, record the new cookie internally */
|
||||
assert( pOp->p1==1 || db->bConcurrent==0 );
|
||||
pDb->pSchema->schema_cookie = pOp->p3;
|
||||
db->mDbFlags |= DBFLAG_SchemaChange;
|
||||
}else if( pOp->p2==BTREE_FILE_FORMAT ){
|
||||
@@ -6626,6 +6657,7 @@ case OP_JournalMode: { /* out2 */
|
||||
|| eNew==PAGER_JOURNALMODE_OFF
|
||||
|| eNew==PAGER_JOURNALMODE_MEMORY
|
||||
|| eNew==PAGER_JOURNALMODE_WAL
|
||||
|| eNew==PAGER_JOURNALMODE_WAL2
|
||||
|| eNew==PAGER_JOURNALMODE_QUERY
|
||||
);
|
||||
assert( pOp->p1>=0 && pOp->p1<db->nDb );
|
||||
@@ -6643,16 +6675,25 @@ case OP_JournalMode: { /* out2 */
|
||||
/* Do not allow a transition to journal_mode=WAL for a database
|
||||
** in temporary storage or if the VFS does not support shared memory
|
||||
*/
|
||||
if( eNew==PAGER_JOURNALMODE_WAL
|
||||
if( isWalMode(eNew)
|
||||
&& (sqlite3Strlen30(zFilename)==0 /* Temp file */
|
||||
|| !sqlite3PagerWalSupported(pPager)) /* No shared-memory support */
|
||||
){
|
||||
eNew = eOld;
|
||||
}
|
||||
|
||||
if( (eNew!=eOld)
|
||||
&& (eOld==PAGER_JOURNALMODE_WAL || eNew==PAGER_JOURNALMODE_WAL)
|
||||
){
|
||||
if( eNew!=eOld && (isWalMode(eNew) || isWalMode(eOld)) ){
|
||||
|
||||
/* Prevent changing directly to wal2 from wal mode. And vice versa. */
|
||||
if( isWalMode(eNew) && isWalMode(eOld) ){
|
||||
rc = SQLITE_ERROR;
|
||||
sqlite3VdbeError(p, "cannot change from %s to %s mode",
|
||||
sqlite3JournalModename(eOld), sqlite3JournalModename(eNew)
|
||||
);
|
||||
goto abort_due_to_error;
|
||||
}
|
||||
|
||||
/* Prevent switching into or out of wal/wal2 mode mid-transaction */
|
||||
if( !db->autoCommit || db->nVdbeRead>1 ){
|
||||
rc = SQLITE_ERROR;
|
||||
sqlite3VdbeError(p,
|
||||
@@ -6660,31 +6701,33 @@ case OP_JournalMode: { /* out2 */
|
||||
(eNew==PAGER_JOURNALMODE_WAL ? "into" : "out of")
|
||||
);
|
||||
goto abort_due_to_error;
|
||||
}else{
|
||||
}
|
||||
|
||||
if( eOld==PAGER_JOURNALMODE_WAL ){
|
||||
/* If leaving WAL mode, close the log file. If successful, the call
|
||||
** to PagerCloseWal() checkpoints and deletes the write-ahead-log
|
||||
** file. An EXCLUSIVE lock may still be held on the database file
|
||||
** after a successful return.
|
||||
*/
|
||||
rc = sqlite3PagerCloseWal(pPager, db);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3PagerSetJournalMode(pPager, eNew);
|
||||
}
|
||||
}else if( eOld==PAGER_JOURNALMODE_MEMORY ){
|
||||
/* Cannot transition directly from MEMORY to WAL. Use mode OFF
|
||||
** as an intermediate */
|
||||
sqlite3PagerSetJournalMode(pPager, PAGER_JOURNALMODE_OFF);
|
||||
}
|
||||
|
||||
/* Open a transaction on the database file. Regardless of the journal
|
||||
** mode, this transaction always uses a rollback journal.
|
||||
if( isWalMode(eOld) ){
|
||||
/* If leaving WAL mode, close the log file. If successful, the call
|
||||
** to PagerCloseWal() checkpoints and deletes the write-ahead-log
|
||||
** file. An EXCLUSIVE lock may still be held on the database file
|
||||
** after a successful return.
|
||||
*/
|
||||
assert( sqlite3BtreeIsInTrans(pBt)==0 );
|
||||
rc = sqlite3PagerCloseWal(pPager, db);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3BtreeSetVersion(pBt, (eNew==PAGER_JOURNALMODE_WAL ? 2 : 1));
|
||||
sqlite3PagerSetJournalMode(pPager, eNew);
|
||||
}
|
||||
}else if( eOld==PAGER_JOURNALMODE_MEMORY ){
|
||||
/* Cannot transition directly from MEMORY to WAL. Use mode OFF
|
||||
** as an intermediate */
|
||||
sqlite3PagerSetJournalMode(pPager, PAGER_JOURNALMODE_OFF);
|
||||
}
|
||||
|
||||
/* Open a transaction on the database file. Regardless of the journal
|
||||
** mode, this transaction always uses a rollback journal.
|
||||
*/
|
||||
assert( sqlite3BtreeIsInTrans(pBt)==0 );
|
||||
if( rc==SQLITE_OK ){
|
||||
/* 1==rollback, 2==wal, 3==wal2 */
|
||||
rc = sqlite3BtreeSetVersion(pBt,
|
||||
1 + isWalMode(eNew) + (eNew==PAGER_JOURNALMODE_WAL2)
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif /* ifndef SQLITE_OMIT_WAL */
|
||||
@@ -6790,6 +6833,14 @@ case OP_Expire: {
|
||||
*/
|
||||
case OP_TableLock: {
|
||||
u8 isWriteLock = (u8)pOp->p3;
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
if( isWriteLock && db->bConcurrent && pOp->p2==1 && pOp->p1!=1 ){
|
||||
rc = SQLITE_ERROR;
|
||||
sqlite3VdbeError(p,
|
||||
"cannot modify database schema within CONCURRENT transaction");
|
||||
goto abort_due_to_error;
|
||||
}
|
||||
#endif
|
||||
if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommit) ){
|
||||
int p1 = pOp->p1;
|
||||
assert( p1>=0 && p1<db->nDb );
|
||||
|
||||
+23
-3
@@ -2488,10 +2488,27 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
|
||||
assert( i!=1 );
|
||||
nTrans++;
|
||||
}
|
||||
rc = sqlite3PagerExclusiveLock(pPager);
|
||||
rc = sqlite3BtreeExclusiveLock(pBt);
|
||||
sqlite3BtreeLeave(pBt);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
if( db->bConcurrent && (rc & 0xFF)==SQLITE_BUSY ){
|
||||
/* An SQLITE_BUSY or SQLITE_BUSY_SNAPSHOT was encountered while
|
||||
** attempting to take the WRITER lock on a wal file. Release the
|
||||
** WRITER locks on all wal files and return early. */
|
||||
for(i=0; i<db->nDb; i++){
|
||||
Btree *pBt = db->aDb[i].pBt;
|
||||
if( sqlite3BtreeIsInTrans(pBt) ){
|
||||
sqlite3BtreeEnter(pBt);
|
||||
sqlite3PagerDropExclusiveLock(sqlite3BtreePager(pBt));
|
||||
sqlite3BtreeLeave(pBt);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
@@ -2887,6 +2904,7 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
|
||||
sqlite3CloseSavepoints(db);
|
||||
db->autoCommit = 1;
|
||||
db->bConcurrent = 0;
|
||||
p->nChange = 0;
|
||||
}
|
||||
}
|
||||
@@ -2922,9 +2940,9 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
** is required. */
|
||||
rc = vdbeCommit(db, p);
|
||||
}
|
||||
if( rc==SQLITE_BUSY && p->readOnly ){
|
||||
if( (rc & 0xFF)==SQLITE_BUSY && p->readOnly ){
|
||||
sqlite3VdbeLeave(p);
|
||||
return SQLITE_BUSY;
|
||||
return rc;
|
||||
}else if( rc!=SQLITE_OK ){
|
||||
p->rc = rc;
|
||||
sqlite3RollbackAll(db, SQLITE_OK);
|
||||
@@ -2949,6 +2967,7 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
|
||||
sqlite3CloseSavepoints(db);
|
||||
db->autoCommit = 1;
|
||||
db->bConcurrent = 0;
|
||||
p->nChange = 0;
|
||||
}
|
||||
}
|
||||
@@ -2970,6 +2989,7 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
|
||||
sqlite3CloseSavepoints(db);
|
||||
db->autoCommit = 1;
|
||||
db->bConcurrent = 0;
|
||||
p->nChange = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#define CKPT_SYNC_FLAGS(X) (((X)>>2)&0x03)
|
||||
|
||||
#ifdef SQLITE_OMIT_WAL
|
||||
# define sqlite3WalOpen(x,y,z) 0
|
||||
# define sqlite3WalOpen(w,x,y,z) 0
|
||||
# define sqlite3WalLimit(x,y)
|
||||
# define sqlite3WalClose(v,w,x,y,z) 0
|
||||
# define sqlite3WalBeginReadTransaction(y,z) 0
|
||||
@@ -34,7 +34,7 @@
|
||||
# define sqlite3WalDbsize(y) 0
|
||||
# define sqlite3WalBeginWriteTransaction(y) 0
|
||||
# define sqlite3WalEndWriteTransaction(x) 0
|
||||
# define sqlite3WalUndo(x,y,z) 0
|
||||
# define sqlite3WalUndo(w,x,y,z) 0
|
||||
# define sqlite3WalSavepoint(y,z)
|
||||
# define sqlite3WalSavepointUndo(y,z) 0
|
||||
# define sqlite3WalFrames(u,v,w,x,y,z) 0
|
||||
@@ -45,6 +45,7 @@
|
||||
# define sqlite3WalFramesize(z) 0
|
||||
# define sqlite3WalFindFrame(x,y,z) 0
|
||||
# define sqlite3WalFile(x) 0
|
||||
# define sqlite3WalJournalMode(x) 0
|
||||
#else
|
||||
|
||||
#define WAL_SAVEPOINT_NDATA 4
|
||||
@@ -55,7 +56,7 @@
|
||||
typedef struct Wal Wal;
|
||||
|
||||
/* Open and close a connection to a write-ahead log. */
|
||||
int sqlite3WalOpen(sqlite3_vfs*, sqlite3_file*, const char *, int, i64, Wal**);
|
||||
int sqlite3WalOpen(sqlite3_vfs*, sqlite3_file*, const char *,int,i64,int,Wal**);
|
||||
int sqlite3WalClose(Wal *pWal, sqlite3*, int sync_flags, int, u8 *);
|
||||
|
||||
/* Set the limiting size of a WAL file. */
|
||||
@@ -83,7 +84,7 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal);
|
||||
int sqlite3WalEndWriteTransaction(Wal *pWal);
|
||||
|
||||
/* Undo any frames written (but not committed) to the log */
|
||||
int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx);
|
||||
int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx, int);
|
||||
|
||||
/* Return an integer that records the current (uncommitted) write
|
||||
** position in the WAL */
|
||||
@@ -136,6 +137,15 @@ int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot);
|
||||
void sqlite3WalSnapshotUnlock(Wal *pWal);
|
||||
#endif
|
||||
|
||||
#ifndef SQLITE_OMIT_CONCURRENT
|
||||
/* Tell the wal layer that we want to commit a concurrent transaction */
|
||||
int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pPg, Bitvec *pRead, Pgno*);
|
||||
|
||||
/* Upgrade the state of the client to take into account changes written
|
||||
** by other connections */
|
||||
int sqlite3WalUpgradeSnapshot(Wal *pWal);
|
||||
#endif /* SQLITE_OMIT_CONCURRENT */
|
||||
|
||||
#ifdef SQLITE_ENABLE_ZIPVFS
|
||||
/* If the WAL file is not empty, return the number of bytes of content
|
||||
** stored in each frame (i.e. the db page-size when the WAL was created).
|
||||
@@ -146,5 +156,14 @@ int sqlite3WalFramesize(Wal *pWal);
|
||||
/* Return the sqlite3_file object for the WAL file */
|
||||
sqlite3_file *sqlite3WalFile(Wal *pWal);
|
||||
|
||||
/* Return the journal mode (WAL or WAL2) used by this Wal object. */
|
||||
int sqlite3WalJournalMode(Wal *pWal);
|
||||
|
||||
/* sqlite3_wal_info() data */
|
||||
int sqlite3WalInfo(Wal *pWal, u32 *pnPrior, u32 *pnFrame);
|
||||
|
||||
/* sqlite3_wal_info() data */
|
||||
int sqlite3WalInfo(Wal *pWal, u32 *pnPrior, u32 *pnFrame);
|
||||
|
||||
#endif /* ifndef SQLITE_OMIT_WAL */
|
||||
#endif /* SQLITE_WAL_H */
|
||||
|
||||
+556
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
** 2016-05-07
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
*/
|
||||
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include "tt3_core.c"
|
||||
|
||||
#ifdef USE_OSINST
|
||||
# include "../src/test_osinst.c"
|
||||
#else
|
||||
# define vfslog_time() 0
|
||||
#endif
|
||||
|
||||
typedef struct Config Config;
|
||||
typedef struct ThreadCtx ThreadCtx;
|
||||
|
||||
#define THREAD_TIME_INSERT 0
|
||||
#define THREAD_TIME_COMMIT 1
|
||||
#define THREAD_TIME_ROLLBACK 2
|
||||
#define THREAD_TIME_WRITER 3
|
||||
#define THREAD_TIME_CKPT 4
|
||||
|
||||
struct ThreadCtx {
|
||||
Config *pConfig;
|
||||
Sqlite *pDb;
|
||||
Error *pErr;
|
||||
sqlite3_int64 aTime[5];
|
||||
};
|
||||
|
||||
struct Config {
|
||||
int nIPT; /* --inserts-per-transaction */
|
||||
int nThread; /* --threads */
|
||||
int nSecond; /* --seconds */
|
||||
int bMutex; /* --mutex */
|
||||
int nAutoCkpt; /* --autockpt */
|
||||
int bRm; /* --rm */
|
||||
int bClearCache; /* --clear-cache */
|
||||
int nMmap; /* mmap limit in MB */
|
||||
char *zFile;
|
||||
int bOsinst; /* True to use osinst */
|
||||
|
||||
ThreadCtx *aCtx; /* Array of size nThread */
|
||||
|
||||
pthread_cond_t cond;
|
||||
pthread_mutex_t mutex;
|
||||
int nCondWait; /* Number of threads waiting on hCond */
|
||||
sqlite3_vfs *pVfs;
|
||||
};
|
||||
|
||||
|
||||
typedef struct VfsWrapperFd VfsWrapperFd;
|
||||
struct VfsWrapperFd {
|
||||
sqlite3_file base; /* Base class */
|
||||
int bWriter; /* True if holding shm WRITER lock */
|
||||
int iTid;
|
||||
Config *pConfig;
|
||||
sqlite3_file *pFd; /* Underlying file descriptor */
|
||||
};
|
||||
|
||||
/* Methods of the wrapper VFS */
|
||||
static int vfsWrapOpen(sqlite3_vfs*, const char*, sqlite3_file*, int, int*);
|
||||
static int vfsWrapDelete(sqlite3_vfs*, const char*, int);
|
||||
static int vfsWrapAccess(sqlite3_vfs*, const char*, int, int*);
|
||||
static int vfsWrapFullPathname(sqlite3_vfs*, const char *, int, char*);
|
||||
static void *vfsWrapDlOpen(sqlite3_vfs*, const char*);
|
||||
static void vfsWrapDlError(sqlite3_vfs*, int, char*);
|
||||
static void (*vfsWrapDlSym(sqlite3_vfs*,void*, const char*))(void);
|
||||
static void vfsWrapDlClose(sqlite3_vfs*, void*);
|
||||
static int vfsWrapRandomness(sqlite3_vfs*, int, char*);
|
||||
static int vfsWrapSleep(sqlite3_vfs*, int);
|
||||
static int vfsWrapCurrentTime(sqlite3_vfs*, double*);
|
||||
static int vfsWrapGetLastError(sqlite3_vfs*, int, char*);
|
||||
static int vfsWrapCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
|
||||
static int vfsWrapSetSystemCall(sqlite3_vfs*, const char*, sqlite3_syscall_ptr);
|
||||
static sqlite3_syscall_ptr vfsWrapGetSystemCall(sqlite3_vfs*, const char*);
|
||||
static const char *vfsWrapNextSystemCall(sqlite3_vfs*, const char*);
|
||||
|
||||
/* Methods of wrapper sqlite3_io_methods object (see vfsWrapOpen()) */
|
||||
static int vfsWrapClose(sqlite3_file*);
|
||||
static int vfsWrapRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
|
||||
static int vfsWrapWrite(sqlite3_file*, const void*, int iAmt, sqlite3_int64);
|
||||
static int vfsWrapTruncate(sqlite3_file*, sqlite3_int64 size);
|
||||
static int vfsWrapSync(sqlite3_file*, int flags);
|
||||
static int vfsWrapFileSize(sqlite3_file*, sqlite3_int64 *pSize);
|
||||
static int vfsWrapLock(sqlite3_file*, int);
|
||||
static int vfsWrapUnlock(sqlite3_file*, int);
|
||||
static int vfsWrapCheckReservedLock(sqlite3_file*, int *pResOut);
|
||||
static int vfsWrapFileControl(sqlite3_file*, int op, void *pArg);
|
||||
static int vfsWrapSectorSize(sqlite3_file*);
|
||||
static int vfsWrapDeviceCharacteristics(sqlite3_file*);
|
||||
static int vfsWrapShmMap(sqlite3_file*, int iPg, int, int, void volatile**);
|
||||
static int vfsWrapShmLock(sqlite3_file*, int offset, int n, int flags);
|
||||
static void vfsWrapShmBarrier(sqlite3_file*);
|
||||
static int vfsWrapShmUnmap(sqlite3_file*, int deleteFlag);
|
||||
static int vfsWrapFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **);
|
||||
static int vfsWrapUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
|
||||
|
||||
static int vfsWrapOpen(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zName,
|
||||
sqlite3_file *pFd,
|
||||
int flags,
|
||||
int *fout
|
||||
){
|
||||
static sqlite3_io_methods methods = {
|
||||
3,
|
||||
vfsWrapClose, vfsWrapRead, vfsWrapWrite,
|
||||
vfsWrapTruncate, vfsWrapSync, vfsWrapFileSize,
|
||||
vfsWrapLock, vfsWrapUnlock, vfsWrapCheckReservedLock,
|
||||
vfsWrapFileControl, vfsWrapSectorSize, vfsWrapDeviceCharacteristics,
|
||||
vfsWrapShmMap, vfsWrapShmLock, vfsWrapShmBarrier,
|
||||
vfsWrapShmUnmap, vfsWrapFetch, vfsWrapUnfetch
|
||||
};
|
||||
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
int rc;
|
||||
|
||||
memset(pWrapper, 0, sizeof(VfsWrapperFd));
|
||||
if( flags & SQLITE_OPEN_MAIN_DB ){
|
||||
pWrapper->iTid = (int)sqlite3_uri_int64(zName, "tid", 0);
|
||||
}
|
||||
|
||||
pWrapper->pFd = (sqlite3_file*)&pWrapper[1];
|
||||
pWrapper->pConfig = pConfig;
|
||||
rc = pConfig->pVfs->xOpen(pConfig->pVfs, zName, pWrapper->pFd, flags, fout);
|
||||
if( rc==SQLITE_OK ){
|
||||
pWrapper->base.pMethods = &methods;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int vfsWrapDelete(sqlite3_vfs *pVfs, const char *a, int b){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xDelete(pConfig->pVfs, a, b);
|
||||
}
|
||||
static int vfsWrapAccess(sqlite3_vfs *pVfs, const char *a, int b, int *c){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xAccess(pConfig->pVfs, a, b, c);
|
||||
}
|
||||
static int vfsWrapFullPathname(sqlite3_vfs *pVfs, const char *a, int b, char*c){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xFullPathname(pConfig->pVfs, a, b, c);
|
||||
}
|
||||
static void *vfsWrapDlOpen(sqlite3_vfs *pVfs, const char *a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xDlOpen(pConfig->pVfs, a);
|
||||
}
|
||||
static void vfsWrapDlError(sqlite3_vfs *pVfs, int a, char *b){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xDlError(pConfig->pVfs, a, b);
|
||||
}
|
||||
static void (*vfsWrapDlSym(sqlite3_vfs *pVfs, void *a, const char *b))(void){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xDlSym(pConfig->pVfs, a, b);
|
||||
}
|
||||
static void vfsWrapDlClose(sqlite3_vfs *pVfs, void *a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xDlClose(pConfig->pVfs, a);
|
||||
}
|
||||
static int vfsWrapRandomness(sqlite3_vfs *pVfs, int a, char *b){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xRandomness(pConfig->pVfs, a, b);
|
||||
}
|
||||
static int vfsWrapSleep(sqlite3_vfs *pVfs, int a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xSleep(pConfig->pVfs, a);
|
||||
}
|
||||
static int vfsWrapCurrentTime(sqlite3_vfs *pVfs, double *a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xCurrentTime(pConfig->pVfs, a);
|
||||
}
|
||||
static int vfsWrapGetLastError(sqlite3_vfs *pVfs, int a, char *b){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xGetLastError(pConfig->pVfs, a, b);
|
||||
}
|
||||
static int vfsWrapCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xCurrentTimeInt64(pConfig->pVfs, a);
|
||||
}
|
||||
static int vfsWrapSetSystemCall(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *a,
|
||||
sqlite3_syscall_ptr b
|
||||
){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xSetSystemCall(pConfig->pVfs, a, b);
|
||||
}
|
||||
static sqlite3_syscall_ptr vfsWrapGetSystemCall(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *a
|
||||
){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xGetSystemCall(pConfig->pVfs, a);
|
||||
}
|
||||
static const char *vfsWrapNextSystemCall(sqlite3_vfs *pVfs, const char *a){
|
||||
Config *pConfig = (Config*)pVfs->pAppData;
|
||||
return pConfig->pVfs->xNextSystemCall(pConfig->pVfs, a);
|
||||
}
|
||||
|
||||
static int vfsWrapClose(sqlite3_file *pFd){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
pWrapper->pFd->pMethods->xClose(pWrapper->pFd);
|
||||
pWrapper->pFd = 0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
static int vfsWrapRead(sqlite3_file *pFd, void *a, int b, sqlite3_int64 c){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xRead(pWrapper->pFd, a, b, c);
|
||||
}
|
||||
static int vfsWrapWrite(
|
||||
sqlite3_file *pFd,
|
||||
const void *a, int b,
|
||||
sqlite3_int64 c
|
||||
){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xWrite(pWrapper->pFd, a, b, c);
|
||||
}
|
||||
static int vfsWrapTruncate(sqlite3_file *pFd, sqlite3_int64 a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xTruncate(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapSync(sqlite3_file *pFd, int a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xSync(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapFileSize(sqlite3_file *pFd, sqlite3_int64 *a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xFileSize(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapLock(sqlite3_file *pFd, int a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xLock(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapUnlock(sqlite3_file *pFd, int a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xUnlock(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapCheckReservedLock(sqlite3_file *pFd, int *a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xCheckReservedLock(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapFileControl(sqlite3_file *pFd, int a, void *b){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xFileControl(pWrapper->pFd, a, b);
|
||||
}
|
||||
static int vfsWrapSectorSize(sqlite3_file *pFd){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xSectorSize(pWrapper->pFd);
|
||||
}
|
||||
static int vfsWrapDeviceCharacteristics(sqlite3_file *pFd){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xDeviceCharacteristics(pWrapper->pFd);
|
||||
}
|
||||
static int vfsWrapShmMap(
|
||||
sqlite3_file *pFd,
|
||||
int a, int b, int c,
|
||||
void volatile **d
|
||||
){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xShmMap(pWrapper->pFd, a, b, c, d);
|
||||
}
|
||||
static int vfsWrapShmLock(sqlite3_file *pFd, int offset, int n, int flags){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
Config *pConfig = pWrapper->pConfig;
|
||||
int bMutex = 0;
|
||||
int rc;
|
||||
|
||||
if( (offset==0 && n==1)
|
||||
&& (flags & SQLITE_SHM_LOCK) && (flags & SQLITE_SHM_EXCLUSIVE)
|
||||
){
|
||||
pthread_mutex_lock(&pConfig->mutex);
|
||||
pWrapper->bWriter = 1;
|
||||
bMutex = 1;
|
||||
if( pWrapper->iTid ){
|
||||
sqlite3_int64 t = vfslog_time();
|
||||
pConfig->aCtx[pWrapper->iTid-1].aTime[THREAD_TIME_WRITER] -= t;
|
||||
}
|
||||
}
|
||||
|
||||
rc = pWrapper->pFd->pMethods->xShmLock(pWrapper->pFd, offset, n, flags);
|
||||
|
||||
if( (rc!=SQLITE_OK && bMutex)
|
||||
|| (offset==0 && (flags & SQLITE_SHM_UNLOCK) && pWrapper->bWriter)
|
||||
){
|
||||
assert( pWrapper->bWriter );
|
||||
pthread_mutex_unlock(&pConfig->mutex);
|
||||
pWrapper->bWriter = 0;
|
||||
if( pWrapper->iTid ){
|
||||
sqlite3_int64 t = vfslog_time();
|
||||
pConfig->aCtx[pWrapper->iTid-1].aTime[THREAD_TIME_WRITER] += t;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
static void vfsWrapShmBarrier(sqlite3_file *pFd){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xShmBarrier(pWrapper->pFd);
|
||||
}
|
||||
static int vfsWrapShmUnmap(sqlite3_file *pFd, int a){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xShmUnmap(pWrapper->pFd, a);
|
||||
}
|
||||
static int vfsWrapFetch(sqlite3_file *pFd, sqlite3_int64 a, int b, void **c){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xFetch(pWrapper->pFd, a, b, c);
|
||||
}
|
||||
static int vfsWrapUnfetch(sqlite3_file *pFd, sqlite3_int64 a, void *b){
|
||||
VfsWrapperFd *pWrapper = (VfsWrapperFd*)pFd;
|
||||
return pWrapper->pFd->pMethods->xUnfetch(pWrapper->pFd, a, b);
|
||||
}
|
||||
|
||||
static void create_vfs(Config *pConfig){
|
||||
static sqlite3_vfs vfs = {
|
||||
3, 0, 0, 0, "wrapper", 0,
|
||||
vfsWrapOpen, vfsWrapDelete, vfsWrapAccess,
|
||||
vfsWrapFullPathname, vfsWrapDlOpen, vfsWrapDlError,
|
||||
vfsWrapDlSym, vfsWrapDlClose, vfsWrapRandomness,
|
||||
vfsWrapSleep, vfsWrapCurrentTime, vfsWrapGetLastError,
|
||||
vfsWrapCurrentTimeInt64, vfsWrapSetSystemCall, vfsWrapGetSystemCall,
|
||||
vfsWrapNextSystemCall
|
||||
};
|
||||
sqlite3_vfs *pVfs;
|
||||
|
||||
pVfs = sqlite3_vfs_find(0);
|
||||
vfs.mxPathname = pVfs->mxPathname;
|
||||
vfs.szOsFile = pVfs->szOsFile + sizeof(VfsWrapperFd);
|
||||
vfs.pAppData = (void*)pConfig;
|
||||
pConfig->pVfs = pVfs;
|
||||
|
||||
sqlite3_vfs_register(&vfs, 1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Wal hook used by connections in thread_main().
|
||||
*/
|
||||
static int thread_wal_hook(
|
||||
void *pArg, /* Pointer to ThreadCtx object */
|
||||
sqlite3 *db,
|
||||
const char *zDb,
|
||||
int nFrame
|
||||
){
|
||||
ThreadCtx *pCtx = (ThreadCtx*)pArg;
|
||||
Config *pConfig = pCtx->pConfig;
|
||||
|
||||
if( pConfig->nAutoCkpt && nFrame>=pConfig->nAutoCkpt ){
|
||||
pCtx->aTime[THREAD_TIME_CKPT] -= vfslog_time();
|
||||
pthread_mutex_lock(&pConfig->mutex);
|
||||
if( pConfig->nCondWait>=0 ){
|
||||
pConfig->nCondWait++;
|
||||
if( pConfig->nCondWait==pConfig->nThread ){
|
||||
execsql(pCtx->pErr, pCtx->pDb, "PRAGMA wal_checkpoint");
|
||||
pthread_cond_broadcast(&pConfig->cond);
|
||||
}else{
|
||||
pthread_cond_wait(&pConfig->cond, &pConfig->mutex);
|
||||
}
|
||||
pConfig->nCondWait--;
|
||||
}
|
||||
pthread_mutex_unlock(&pConfig->mutex);
|
||||
pCtx->aTime[THREAD_TIME_CKPT] += vfslog_time();
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *thread_main(int iTid, void *pArg){
|
||||
Config *pConfig = (Config*)pArg;
|
||||
Error err = {0}; /* Error code and message */
|
||||
Sqlite db = {0}; /* SQLite database connection */
|
||||
int nAttempt = 0; /* Attempted transactions */
|
||||
int nCommit = 0; /* Successful transactions */
|
||||
int j;
|
||||
ThreadCtx *pCtx = &pConfig->aCtx[iTid-1];
|
||||
char *zUri = 0;
|
||||
|
||||
#ifdef USE_OSINST
|
||||
char *zOsinstName = 0;
|
||||
char *zLogName = 0;
|
||||
if( pConfig->bOsinst ){
|
||||
zOsinstName = sqlite3_mprintf("osinst%d", iTid);
|
||||
zLogName = sqlite3_mprintf("bc_test1.log.%d.%d", (int)getpid(), iTid);
|
||||
zUri = sqlite3_mprintf(
|
||||
"file:%s?vfs=%s&tid=%d", pConfig->zFile, zOsinstName, iTid
|
||||
);
|
||||
sqlite3_vfslog_new(zOsinstName, 0, zLogName);
|
||||
opendb(&err, &db, zUri, 0);
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
zUri = sqlite3_mprintf("file:%s?tid=%d", pConfig->zFile, iTid);
|
||||
opendb(&err, &db, zUri, 0);
|
||||
}
|
||||
|
||||
sqlite3_busy_handler(db.db, 0, 0);
|
||||
sql_script_printf(&err, &db,
|
||||
"PRAGMA wal_autocheckpoint = 0;"
|
||||
"PRAGMA synchronous = 0;"
|
||||
"PRAGMA mmap_size = %lld;",
|
||||
(i64)(pConfig->nMmap) * 1024 * 1024
|
||||
);
|
||||
|
||||
pCtx->pConfig = pConfig;
|
||||
pCtx->pErr = &err;
|
||||
pCtx->pDb = &db;
|
||||
sqlite3_wal_hook(db.db, thread_wal_hook, (void*)pCtx);
|
||||
|
||||
while( !timetostop(&err) ){
|
||||
execsql(&err, &db, "BEGIN CONCURRENT");
|
||||
|
||||
pCtx->aTime[THREAD_TIME_INSERT] -= vfslog_time();
|
||||
for(j=0; j<pConfig->nIPT; j++){
|
||||
execsql(&err, &db,
|
||||
"INSERT INTO t1 VALUES"
|
||||
"(randomblob(10), randomblob(20), randomblob(30), randomblob(200))"
|
||||
);
|
||||
}
|
||||
pCtx->aTime[THREAD_TIME_INSERT] += vfslog_time();
|
||||
|
||||
pCtx->aTime[THREAD_TIME_COMMIT] -= vfslog_time();
|
||||
execsql(&err, &db, "COMMIT");
|
||||
pCtx->aTime[THREAD_TIME_COMMIT] += vfslog_time();
|
||||
|
||||
pCtx->aTime[THREAD_TIME_ROLLBACK] -= vfslog_time();
|
||||
nAttempt++;
|
||||
if( err.rc==SQLITE_OK ){
|
||||
nCommit++;
|
||||
}else{
|
||||
clear_error(&err, SQLITE_BUSY);
|
||||
execsql(&err, &db, "ROLLBACK");
|
||||
}
|
||||
pCtx->aTime[THREAD_TIME_ROLLBACK] += vfslog_time();
|
||||
|
||||
if( pConfig->bClearCache ){
|
||||
sqlite3_db_release_memory(db.db);
|
||||
}
|
||||
}
|
||||
|
||||
closedb(&err, &db);
|
||||
|
||||
#ifdef USE_OSINST
|
||||
if( pConfig->bOsinst ){
|
||||
sqlite3_vfslog_finalize(zOsinstName);
|
||||
sqlite3_free(zOsinstName);
|
||||
sqlite3_free(zLogName);
|
||||
}
|
||||
#endif
|
||||
sqlite3_free(zUri);
|
||||
|
||||
pthread_mutex_lock(&pConfig->mutex);
|
||||
pConfig->nCondWait = -1;
|
||||
pthread_cond_broadcast(&pConfig->cond);
|
||||
pthread_mutex_unlock(&pConfig->mutex);
|
||||
|
||||
return sqlite3_mprintf("commits: %d/%d insert: %dms"
|
||||
" commit: %dms"
|
||||
" rollback: %dms"
|
||||
" writer: %dms"
|
||||
" checkpoint: %dms",
|
||||
nCommit, nAttempt,
|
||||
(int)(pCtx->aTime[THREAD_TIME_INSERT]/1000),
|
||||
(int)(pCtx->aTime[THREAD_TIME_COMMIT]/1000),
|
||||
(int)(pCtx->aTime[THREAD_TIME_ROLLBACK]/1000),
|
||||
(int)(pCtx->aTime[THREAD_TIME_WRITER]/1000),
|
||||
(int)(pCtx->aTime[THREAD_TIME_CKPT]/1000)
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv){
|
||||
Error err = {0}; /* Error code and message */
|
||||
Sqlite db = {0}; /* SQLite database connection */
|
||||
Threadset threads = {0}; /* Test threads */
|
||||
Config conf = {5, 3, 5};
|
||||
int i;
|
||||
|
||||
CmdlineArg apArg[] = {
|
||||
{ "-seconds", CMDLINE_INT, offsetof(Config, nSecond) },
|
||||
{ "-inserts", CMDLINE_INT, offsetof(Config, nIPT) },
|
||||
{ "-threads", CMDLINE_INT, offsetof(Config, nThread) },
|
||||
{ "-mutex", CMDLINE_BOOL, offsetof(Config, bMutex) },
|
||||
{ "-rm", CMDLINE_BOOL, offsetof(Config, bRm) },
|
||||
{ "-autockpt",CMDLINE_INT, offsetof(Config, nAutoCkpt) },
|
||||
{ "-mmap", CMDLINE_INT, offsetof(Config, nMmap) },
|
||||
{ "-clear-cache", CMDLINE_BOOL, offsetof(Config, bClearCache) },
|
||||
{ "-file", CMDLINE_STRING, offsetof(Config, zFile) },
|
||||
{ "-osinst", CMDLINE_BOOL, offsetof(Config, bOsinst) },
|
||||
{ 0, 0, 0 }
|
||||
};
|
||||
|
||||
conf.nAutoCkpt = 1000;
|
||||
cmdline_process(apArg, argc, argv, (void*)&conf);
|
||||
if( err.rc==SQLITE_OK ){
|
||||
char *z = cmdline_construct(apArg, (void*)&conf);
|
||||
printf("With: %s\n", z);
|
||||
sqlite3_free(z);
|
||||
}
|
||||
if( conf.zFile==0 ){
|
||||
conf.zFile = "xyz.db";
|
||||
}
|
||||
|
||||
/* Create the special VFS - "wrapper". And the mutex and condition
|
||||
** variable. */
|
||||
create_vfs(&conf);
|
||||
pthread_mutex_init(&conf.mutex, 0);
|
||||
pthread_cond_init(&conf.cond, 0);
|
||||
|
||||
conf.aCtx = sqlite3_malloc(sizeof(ThreadCtx) * conf.nThread);
|
||||
memset(conf.aCtx, 0, sizeof(ThreadCtx) * conf.nThread);
|
||||
|
||||
/* Ensure the schema has been created */
|
||||
opendb(&err, &db, conf.zFile, conf.bRm);
|
||||
sql_script(&err, &db,
|
||||
"PRAGMA journal_mode = wal;"
|
||||
"CREATE TABLE IF NOT EXISTS t1(a PRIMARY KEY, b, c, d) WITHOUT ROWID;"
|
||||
"CREATE INDEX IF NOT EXISTS t1b ON t1(b);"
|
||||
"CREATE INDEX IF NOT EXISTS t1c ON t1(c);"
|
||||
);
|
||||
|
||||
setstoptime(&err, conf.nSecond*1000);
|
||||
if( conf.nThread==1 ){
|
||||
char *z = thread_main(1, (void*)&conf);
|
||||
printf("Thread 0 says: %s\n", (z==0 ? "..." : z));
|
||||
fflush(stdout);
|
||||
}else{
|
||||
for(i=0; i<conf.nThread; i++){
|
||||
launch_thread(&err, &threads, thread_main, (void*)&conf);
|
||||
}
|
||||
join_all_threads(&err, &threads);
|
||||
}
|
||||
|
||||
if( err.rc==SQLITE_OK ){
|
||||
printf("Database is %dK\n", (int)(filesize(&err, conf.zFile) / 1024));
|
||||
}
|
||||
if( err.rc==SQLITE_OK ){
|
||||
char *zWal = sqlite3_mprintf("%s-wal", conf.zFile);
|
||||
printf("Wal file is %dK\n", (int)(filesize(&err, zWal) / 1024));
|
||||
}
|
||||
|
||||
closedb(&err, &db);
|
||||
print_and_free_err(&err);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
# 2015 Aug 25
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# This file contains fault injection tests designed to test the concurrent
|
||||
# transactions feature.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
set testprefix concfault
|
||||
|
||||
# This test will not work with an in-memory journal, as the database will
|
||||
# become corrupt if an error is injected into a transaction after it starts
|
||||
# writing data out to the db file.
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_test 1-pre1 {
|
||||
execsql {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
DELETE FROM t1 WHERE rowid%2;
|
||||
}
|
||||
faultsim_save_and_close
|
||||
} {}
|
||||
|
||||
do_faultsim_test 1.1 -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
|
||||
COMMIT;
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
catchsql { ROLLBACK }
|
||||
faultsim_integrity_check
|
||||
}
|
||||
|
||||
do_faultsim_test 1.2 -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
|
||||
ROLLBACK;
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
catchsql { ROLLBACK }
|
||||
faultsim_integrity_check
|
||||
}
|
||||
|
||||
do_faultsim_test 1.3 -prep {
|
||||
faultsim_restore_and_reopen
|
||||
} -body {
|
||||
execsql {
|
||||
BEGIN CONCURRENT;
|
||||
DELETE FROM t1;
|
||||
COMMIT;
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
catchsql { ROLLBACK }
|
||||
faultsim_integrity_check
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# 2018 Dec 28
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# This file contains fault injection tests designed to test the concurrent
|
||||
# transactions feature.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
set testprefix concfault2
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA auto_vacuum = 0;
|
||||
PRAGMA journal_mode = wal2;
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(randomblob(1000), randomblob(100));
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1;
|
||||
DELETE FROM t1 WHERE rowid%2;
|
||||
} {wal2}
|
||||
|
||||
do_test 1.1 {
|
||||
list [expr [file size test.db-wal]>75000] [file size test.db-shm]
|
||||
} {1 32768}
|
||||
|
||||
faultsim_save_and_close
|
||||
|
||||
do_faultsim_test 1 -prep {
|
||||
faultsim_restore_and_reopen
|
||||
execsql {
|
||||
SELECT * FROM t1;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES(1, 2);
|
||||
}
|
||||
sqlite3 db2 test.db
|
||||
execsql {
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
INSERT INTO t1 VALUES(randomblob(1000), randomblob(1000));
|
||||
} db2
|
||||
db2 close
|
||||
} -body {
|
||||
execsql { COMMIT }
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
catchsql { ROLLBACK }
|
||||
set res [catchsql { SELECT count(*) FROM t1 }]
|
||||
if {$res!="0 9"} { error "expected {0 9} got {$res}" }
|
||||
faultsim_integrity_check
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,678 @@
|
||||
# 2015 July 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
set ::testprefix concurrent
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
} {wal}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
CREATE TABLE t1(k INTEGER PRIMARY KEY, v);
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(1, 'abcd');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
SELECT * FROM t1;
|
||||
} {1 abcd}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(2, 'efgh');
|
||||
ROLLBACK;
|
||||
}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
SELECT * FROM t1;
|
||||
} {1 abcd}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# CONCURRENT transactions cannot do cache spills.
|
||||
#
|
||||
foreach {tn trans spill} {
|
||||
1 {BEGIN CONCURRENT} 0
|
||||
2 {BEGIN} 1
|
||||
} {
|
||||
do_test 1.5.$tn {
|
||||
sqlite3 db2 test.db
|
||||
set walsz [file size test.db-wal]
|
||||
|
||||
execsql { PRAGMA cache_size = 10 } db2
|
||||
execsql $trans db2
|
||||
execsql {
|
||||
WITH cnt(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<50)
|
||||
INSERT INTO t1(v) SELECT randomblob(900) FROM cnt;
|
||||
} db2
|
||||
|
||||
expr {[file size test.db-wal]==$walsz}
|
||||
} [expr !$spill]
|
||||
|
||||
execsql ROLLBACK db2
|
||||
db2 close
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# CONCURRENT transactions man not be committed while there are active
|
||||
# readers.
|
||||
do_execsql_test 1.6.setup {
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1(a, b);
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(3, 4);
|
||||
INSERT INTO t1 VALUES(5, 6);
|
||||
}
|
||||
foreach {tn trans commit_ok} {
|
||||
1 {BEGIN CONCURRENT} 0
|
||||
2 {BEGIN} 1
|
||||
} {
|
||||
do_test 1.6.$tn.1 {
|
||||
set stmt [sqlite3_prepare db "SELECT * FROM t1" -1 dummy]
|
||||
sqlite3_step $stmt
|
||||
} SQLITE_ROW
|
||||
do_test 1.6.$tn.2 {
|
||||
execsql $trans
|
||||
execsql { INSERT INTO t1 VALUES(7, 8) }
|
||||
} {}
|
||||
|
||||
if { $commit_ok } {
|
||||
do_test 1.6.$tn.3 { catchsql COMMIT } {0 {}}
|
||||
} else {
|
||||
do_test 1.6.$tn.4 { catchsql COMMIT } {/1 {cannot commit transaction .*}/}
|
||||
}
|
||||
|
||||
sqlite3_finalize $stmt
|
||||
catchsql ROLLBACK
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# CONCURRENT transactions may not modify the db schema.
|
||||
#
|
||||
foreach {tn sql} {
|
||||
1 { CREATE TABLE xx(a, b) }
|
||||
2 { DROP TABLE t1 }
|
||||
3 { CREATE INDEX i1 ON t1(a) }
|
||||
4 { CREATE VIEW v1 AS SELECT * FROM t1 }
|
||||
} {
|
||||
do_catchsql_test 1.7.0.$tn.1 "
|
||||
BEGIN CONCURRENT;
|
||||
$sql
|
||||
" {1 {cannot modify database schema within CONCURRENT transaction}}
|
||||
|
||||
do_execsql_test 1.7.0.$tn.2 {
|
||||
SELECT sql FROM sqlite_master;
|
||||
SELECT sql FROM sqlite_temp_master;
|
||||
} {{CREATE TABLE t1(a, b)}}
|
||||
|
||||
do_execsql_test 1.7.0.$tn.3 COMMIT
|
||||
}
|
||||
|
||||
# Except the temp db schema.
|
||||
foreach {tn sql} {
|
||||
1 { CREATE TEMP TABLE xx(a, b) }
|
||||
2 { DROP TABLE xx }
|
||||
3 { CREATE TEMP TABLE yy(a, b) }
|
||||
4 { CREATE VIEW temp.v1 AS SELECT * FROM t1 }
|
||||
5 { CREATE INDEX yyi1 ON yy(a); }
|
||||
6 { CREATE TABLE temp.zz(a, b) }
|
||||
} {
|
||||
do_catchsql_test 1.7.1.$tn.1 "
|
||||
BEGIN CONCURRENT;
|
||||
$sql
|
||||
" {0 {}}
|
||||
|
||||
do_execsql_test 1.7.1.$tn.2 COMMIT
|
||||
}
|
||||
|
||||
|
||||
do_execsql_test 1.7.1.x {
|
||||
SELECT sql FROM sqlite_master;
|
||||
SELECT sql FROM sqlite_temp_master;
|
||||
} {
|
||||
{CREATE TABLE t1(a, b)}
|
||||
{CREATE TABLE yy(a, b)}
|
||||
{CREATE VIEW v1 AS SELECT * FROM t1}
|
||||
{CREATE INDEX yyi1 ON yy(a)}
|
||||
{CREATE TABLE zz(a, b)}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# If an auto-vacuum database is written within an CONCURRENT transaction, it
|
||||
# is handled in the same way as for a non-CONCURRENT transaction.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 1.8.1 {
|
||||
PRAGMA auto_vacuum = 1;
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x, y);
|
||||
INSERT INTO t1 VALUES('x', 'y');
|
||||
} {wal}
|
||||
|
||||
do_execsql_test 1.8.2 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t1;
|
||||
COMMIT;
|
||||
} {x y}
|
||||
|
||||
do_catchsql_test 1.8.3 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES('a', 'b');
|
||||
} {0 {}}
|
||||
|
||||
do_test 1.8.4 {
|
||||
sqlite3 db2 test.db
|
||||
catchsql {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES('c', 'd');
|
||||
} db2
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test 1.8.5 {
|
||||
db eval COMMIT
|
||||
db2 eval COMMIT
|
||||
} {}
|
||||
db close
|
||||
db2 close
|
||||
|
||||
do_multiclient_test tn {
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1. Start an CONCURRENT transaction using [db1].
|
||||
#
|
||||
# 2. Start and then rollback a regular transaction using [db2]. This
|
||||
# can be done as the ongoing [db1] transaction is CONCURRENT.
|
||||
#
|
||||
# 3. The [db1] transaction can now be committed, as [db2] has relinquished
|
||||
# the write lock.
|
||||
#
|
||||
do_test 2.$tn.1.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(k INTEGER PRIMARY KEY, v);
|
||||
INSERT INTO t1 VALUES(1, 'one');
|
||||
}
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(2, 'two');
|
||||
}
|
||||
code1 { sqlite3_get_autocommit db }
|
||||
} 0
|
||||
|
||||
do_test 2.$tn.1.2 {
|
||||
sql2 {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(3, 'three');
|
||||
ROLLBACK;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.1.3 {
|
||||
sql1 COMMIT
|
||||
sql2 { SELECT * FROM t1 }
|
||||
} {1 one 2 two}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1. Start an CONCURRENT transaction using [db1].
|
||||
#
|
||||
# 2. Commit a transaction using [db2].
|
||||
#
|
||||
# 3. Try to commit with [db1]. Check that SQLITE_BUSY_SNAPSHOT is returned,
|
||||
# and the transaction is not rolled back.
|
||||
#
|
||||
do_test 2.$tn.2.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(-1, 'hello world');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.2.2 {
|
||||
sql2 {
|
||||
INSERT INTO t1 VALUES(3, 'three');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.2.3.1 {
|
||||
set rc [catch { sql1 COMMIT } msg]
|
||||
list $rc $msg
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test 2.$tn.2.3.2 {
|
||||
code1 { list [sqlite3_extended_errcode db] [sqlite3_get_autocommit db] }
|
||||
} {SQLITE_BUSY_SNAPSHOT 0}
|
||||
|
||||
do_test 2.$tn.2.3.3 {
|
||||
sql1 {
|
||||
SELECT * FROM t1;
|
||||
ROLLBACK;
|
||||
}
|
||||
} {-1 {hello world} 1 one 2 two}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1. Start an CONCURRENT transaction using [db1].
|
||||
#
|
||||
# 2. Open a transaction using [db2].
|
||||
#
|
||||
# 3. Try to commit with [db1]. Check that SQLITE_BUSY is returned,
|
||||
# and the transaction is not rolled back.
|
||||
#
|
||||
# 4. Have [db2] roll its transaction back. Then check that [db1] can
|
||||
# commit.
|
||||
#
|
||||
do_test 2.$tn.3.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(4, 'four');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.3.2 {
|
||||
sql2 {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(-1, 'xyz');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.3.3.1 {
|
||||
set rc [catch { sql1 COMMIT } msg]
|
||||
list $rc $msg
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test 2.$tn.3.3.2 {
|
||||
code1 { list [sqlite3_extended_errcode db] [sqlite3_get_autocommit db] }
|
||||
} {SQLITE_BUSY 0}
|
||||
|
||||
do_test 2.$tn.3.3.3 {
|
||||
sql1 { SELECT * FROM t1; }
|
||||
} {1 one 2 two 3 three 4 four}
|
||||
|
||||
do_test 2.$tn.3.4 {
|
||||
sql2 ROLLBACK
|
||||
sql1 COMMIT
|
||||
sql1 { SELECT * FROM t1; }
|
||||
} {1 one 2 two 3 three 4 four}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# 1. Create a second table - t2.
|
||||
#
|
||||
# 2. Write to t1 with [db] and t2 with [db2].
|
||||
#
|
||||
# 3. See if it worked.
|
||||
#
|
||||
do_test 2.$tn.4.1 {
|
||||
sql1 { CREATE TABLE t2(a, b) }
|
||||
} {}
|
||||
do_test 2.$tn.4.2 {
|
||||
sql2 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES('i', 'n');
|
||||
}
|
||||
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(5, 'five');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
sql2 COMMIT
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.4.3.1 {
|
||||
sql2 {SELECT * FROM t1}
|
||||
} {1 one 2 two 3 three 4 four 5 five}
|
||||
do_test 2.$tn.4.3.2 {
|
||||
sql1 {SELECT * FROM t1}
|
||||
} {1 one 2 two 3 three 4 four 5 five}
|
||||
|
||||
do_test 2.$tn.4.3.3 { sql2 {SELECT * FROM t2} } {i n}
|
||||
do_test 2.$tn.4.3.4 { sql1 {SELECT * FROM t2} } {i n}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# The "schema cookie" issue.
|
||||
#
|
||||
# 1. Begin and CONCURRENT write to "t1" using [db]
|
||||
#
|
||||
# 2. Create an index on t1 using [db2].
|
||||
#
|
||||
# 3. Attempt to commit the CONCURRENT write. This is an SQLITE_BUSY_SNAPSHOT,
|
||||
# even though there is no page collision.
|
||||
#
|
||||
do_test 2.$tn.5.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(6, 'six');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.5.2 {
|
||||
sql2 { CREATE INDEX i1 ON t1(v); }
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.5.3 {
|
||||
list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
|
||||
} {1 {database is locked} SQLITE_BUSY_SNAPSHOT}
|
||||
|
||||
do_test 2.$tn.5.4 {
|
||||
sql2 { PRAGMA integrity_check }
|
||||
} {ok}
|
||||
catch { sql1 ROLLBACK }
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
#
|
||||
# 1. Begin an CONCURRENT write to "t1" using [db]
|
||||
#
|
||||
# 2. Lots of inserts into t2. Enough to grow the db file and modify page 1.
|
||||
#
|
||||
# 3. Check that the CONCURRENT transaction can not be committed.
|
||||
#
|
||||
do_test 2.$tn.6.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(6, 'six');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.6.2 {
|
||||
sql2 {
|
||||
WITH src(a,b) AS (
|
||||
VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000
|
||||
) INSERT INTO t2 SELECT * FROM src;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.6.3 {
|
||||
sql1 { SELECT count(*) FROM t2 }
|
||||
list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
|
||||
} {1 {database is locked} SQLITE_BUSY_SNAPSHOT}
|
||||
sql1 ROLLBACK
|
||||
|
||||
do_test 2.$tn.6.4 {
|
||||
sql1 {
|
||||
SELECT count(*) FROM t1;
|
||||
SELECT count(*) FROM t2;
|
||||
}
|
||||
} {5 10001}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
#
|
||||
# 1. Begin an big CONCURRENT write to "t1" using [db] - large enough to
|
||||
# grow the db file.
|
||||
#
|
||||
# 2. Lots of inserts into t2. Also enough to grow the db file.
|
||||
#
|
||||
# 3. Check that the CONCURRENT transaction cannot be committed (due to a clash
|
||||
# on page 1 - the db size field).
|
||||
#
|
||||
do_test 2.$tn.7.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
WITH src(a,b) AS (
|
||||
VALUES(10000,10000) UNION ALL SELECT a+1,b+1 FROM src WHERE a<20000
|
||||
) INSERT INTO t1 SELECT * FROM src;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.7.2 {
|
||||
sql2 {
|
||||
WITH src(a,b) AS (
|
||||
VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000
|
||||
) INSERT INTO t2 SELECT * FROM src;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.7.3 {
|
||||
list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
|
||||
} {0 {} SQLITE_OK}
|
||||
|
||||
do_test 2.$tn.7.4 { sql3 { PRAGMA integrity_check } } ok
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Concurrent transactions may not modify the user_version or application_id.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x, y);
|
||||
INSERT INTO t1 VALUES('a', 'b');
|
||||
PRAGMA user_version = 10;
|
||||
} {wal}
|
||||
do_execsql_test 3.1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES('c', 'd');
|
||||
SELECT * FROM t1;
|
||||
} {a b c d}
|
||||
do_catchsql_test 3.2 {
|
||||
PRAGMA user_version = 11;
|
||||
} {1 {cannot modify user_version within CONCURRENT transaction}}
|
||||
do_execsql_test 3.3 {
|
||||
PRAGMA user_version;
|
||||
SELECT * FROM t1;
|
||||
} {10 a b c d}
|
||||
do_catchsql_test 3.4 {
|
||||
PRAGMA application_id = 11;
|
||||
} {1 {cannot modify application_id within CONCURRENT transaction}}
|
||||
do_execsql_test 3.5 {
|
||||
COMMIT;
|
||||
PRAGMA user_version;
|
||||
PRAGMA application_id;
|
||||
SELECT * FROM t1;
|
||||
} {10 0 a b c d}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# However, another transaction modifying the user_version or application_id
|
||||
# should not cause a conflict. And committing a concurrent transaction does not
|
||||
# clobber the modification - even if the concurrent transaction allocates or
|
||||
# frees database pages.
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 4.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE ttt(y UNIQUE, z UNIQUE);
|
||||
PRAGMA user_version = 14;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO ttt VALUES('y', 'z');
|
||||
}
|
||||
} {wal}
|
||||
do_test 4.$tn.2 {
|
||||
sql2 { PRAGMA user_version = 16 }
|
||||
sql1 COMMIT
|
||||
sql1 { PRAGMA user_version }
|
||||
} {16}
|
||||
|
||||
do_test 4.$tn.3 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO ttt VALUES(randomblob(10000), randomblob(4));
|
||||
PRAGMA user_version;
|
||||
}
|
||||
} {16}
|
||||
do_test 4.$tn.4 {
|
||||
sql2 { PRAGMA user_version = 1234 }
|
||||
sql1 {
|
||||
PRAGMA user_version;
|
||||
COMMIT;
|
||||
PRAGMA user_version;
|
||||
PRAGMA integrity_check;
|
||||
}
|
||||
} {16 1234 ok}
|
||||
|
||||
do_test 4.$tn.5 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
DELETE FROM ttt;
|
||||
PRAGMA user_version;
|
||||
}
|
||||
} {1234}
|
||||
do_test 4.$tn.4 {
|
||||
sql2 { PRAGMA user_version = 5678 }
|
||||
sql1 {
|
||||
PRAGMA user_version;
|
||||
COMMIT;
|
||||
PRAGMA user_version;
|
||||
PRAGMA integrity_check;
|
||||
}
|
||||
} {1234 5678 ok}
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 5.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE tt(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO tt VALUES(1, randomblob(400));
|
||||
BEGIN CONCURRENT;
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 5.$tn.2 {
|
||||
sql1 { UPDATE t2 SET b=5 WHERE a=3 }
|
||||
sql2 { INSERT INTO tt VALUES(2, randomblob(6000)) }
|
||||
} {}
|
||||
|
||||
do_test 5.$tn.3 {
|
||||
sql1 { COMMIT }
|
||||
} {}
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 6.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(1, 'one');
|
||||
INSERT INTO t2 VALUES(2, 'two');
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 6.$tn.2 {
|
||||
sql2 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t2;
|
||||
INSERT INTO t1 VALUES(3, 'three');
|
||||
}
|
||||
} {2 two}
|
||||
|
||||
do_test 6.$tn.3 {
|
||||
sql1 {
|
||||
INSERT INTO t2 VALUES(3, 'three');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 6.$tn.2 {
|
||||
list [catch { sql2 { COMMIT } } msg] $msg
|
||||
} {1 {database is locked}}
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 7.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t1 SELECT NULL, randomblob(400) FROM s;
|
||||
|
||||
CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<50000)
|
||||
INSERT INTO t2 SELECT NULL, randomblob(400) FROM s;
|
||||
|
||||
CREATE TABLE t3(a INTEGER PRIMARY KEY, b);
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t3 SELECT NULL, randomblob(400) FROM s;
|
||||
|
||||
CREATE TABLE t4(a INTEGER PRIMARY KEY, b);
|
||||
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
do_test 7.$tn.2 {
|
||||
sql2 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t1;
|
||||
INSERT INTO t4 VALUES(1, 2);
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
do_test 7.$tn.3 {
|
||||
sql3 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t3;
|
||||
INSERT INTO t4 VALUES(1, 2);
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
do_test 7.$tn.4 {
|
||||
sql1 {
|
||||
UPDATE t1 SET b=randomblob(400);
|
||||
UPDATE t2 SET b=randomblob(400);
|
||||
UPDATE t3 SET b=randomblob(400);
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 7.$tn.5 {
|
||||
csql2 { COMMIT }
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test 7.$tn.6 {
|
||||
csql3 { COMMIT }
|
||||
} {1 {database is locked}}
|
||||
|
||||
|
||||
csql2 ROLLBACK
|
||||
csql3 ROLLBACK
|
||||
|
||||
# The following test works with $tn==1 (sql2 and sql3 use separate
|
||||
# processes), but is quite slow. So only run it with $tn==2 (all
|
||||
# connections in the same process).
|
||||
#
|
||||
if {$tn==2} {
|
||||
do_test 7.$tn.7 {
|
||||
for {set i 1} {$i < 10000} {incr i} {
|
||||
sql3 {
|
||||
PRAGMA wal_checkpoint;
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t3;
|
||||
INSERT INTO t4 VALUES(1, 2);
|
||||
}
|
||||
|
||||
sql1 {
|
||||
UPDATE t2 SET b = randomblob(400) WHERE rowid <= $i;
|
||||
UPDATE t3 SET b = randomblob(400) WHERE rowid = 1;
|
||||
}
|
||||
|
||||
if {[csql3 COMMIT]!={1 {database is locked}}} {
|
||||
error "Failed at i=$i"
|
||||
}
|
||||
csql3 ROLLBACK
|
||||
}
|
||||
} {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,627 @@
|
||||
# 2015 July 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Miscellaneous tests for transactions started with BEGIN CONCURRENT.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
set ::testprefix concurrent2
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_test 0.1 {
|
||||
llength [sqlite3_wal_info db main]
|
||||
} {2}
|
||||
|
||||
do_multiclient_test tn {
|
||||
|
||||
do_test 1.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(y);
|
||||
}
|
||||
} {wal}
|
||||
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
|
||||
# Test that an CONCURRENT transaction that allocates/frees no pages does
|
||||
# not conflict with a transaction that does allocate pages.
|
||||
do_test 1.$tn.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(4);
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
}
|
||||
sql1 {
|
||||
COMMIT;
|
||||
}
|
||||
} {}
|
||||
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
|
||||
# But that an CONCURRENT transaction does conflict with a transaction
|
||||
# that modifies the db schema.
|
||||
do_test 1.$tn.3 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(5);
|
||||
}
|
||||
sql2 {
|
||||
CREATE TABLE t3(z);
|
||||
}
|
||||
list [catch { sql1 COMMIT } msg] $msg
|
||||
} {1 {database is locked}}
|
||||
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
|
||||
# Test that an CONCURRENT transaction that allocates at least one page
|
||||
# does not conflict with a transaction that allocates no pages.
|
||||
do_test 1.$tn.4 {
|
||||
sql1 {
|
||||
ROLLBACK;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t2 VALUES(8);
|
||||
}
|
||||
sql1 {
|
||||
COMMIT;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 2.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x UNIQUE);
|
||||
CREATE TABLE t2(y UNIQUE);
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 2.$tn.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
}
|
||||
sql1 COMMIT
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.3 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
|
||||
do_test 2.$tn.4 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
DELETE FROM t1;
|
||||
}
|
||||
sql2 {
|
||||
DELETE FROM t2;
|
||||
}
|
||||
sql1 COMMIT
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
|
||||
do_test 2.$tn.6 {
|
||||
sql1 {
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
DELETE FROM t1 WHERE rowid=1;
|
||||
}
|
||||
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
DELETE FROM t1 WHERE rowid=2;
|
||||
}
|
||||
|
||||
sql2 {
|
||||
DELETE FROM t2;
|
||||
}
|
||||
|
||||
sql1 COMMIT
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.7 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# When an CONCURRENT transaction is opened on a database, the nFree and
|
||||
# iTrunk header fields of the cached version of page 1 are both set
|
||||
# to 0. This allows an CONCURRENT transaction to use its own private
|
||||
# free-page-list, which is merged with the main database free-list when
|
||||
# the transaction is committed.
|
||||
#
|
||||
# The following tests check that nFree/iTrunk are correctly restored if
|
||||
# an CONCURRENT transaction is rolled back, and that savepoint rollbacks
|
||||
# that occur within CONCURRENT transactions do not incorrectly restore
|
||||
# these fields to their on-disk values.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x, y);
|
||||
INSERT INTO t1 VALUES(randomblob(1500), randomblob(1500));
|
||||
DELETE FROM t1;
|
||||
} {wal}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
ROLLBACK;
|
||||
}
|
||||
|
||||
do_execsql_test 3.2 { PRAGMA integrity_check } {ok}
|
||||
do_execsql_test 3.3 { PRAGMA freelist_count } {2}
|
||||
|
||||
do_execsql_test 3.4.1 {
|
||||
BEGIN CONCURRENT;
|
||||
PRAGMA freelist_count;
|
||||
} {2}
|
||||
do_execsql_test 3.4.2 {
|
||||
SAVEPOINT xyz;
|
||||
INSERT INTO t1 VALUES(randomblob(1500), NULL);
|
||||
PRAGMA freelist_count;
|
||||
} {0}
|
||||
do_execsql_test 3.4.3 {
|
||||
ROLLBACK TO xyz;
|
||||
} {}
|
||||
do_execsql_test 3.4.4 { PRAGMA freelist_count } {0}
|
||||
do_execsql_test 3.4.5 { COMMIT; PRAGMA freelist_count } {2}
|
||||
do_execsql_test 3.4.6 { PRAGMA integrity_check } {ok}
|
||||
|
||||
do_execsql_test 3.5.1 {
|
||||
BEGIN CONCURRENT;
|
||||
UPDATE t1 SET x=randomblob(10) WHERE y=555;
|
||||
PRAGMA freelist_count;
|
||||
} {0}
|
||||
do_execsql_test 3.5.2 {
|
||||
ROLLBACK;
|
||||
PRAGMA freelist_count;
|
||||
} {2}
|
||||
do_execsql_test 3.5.3 { PRAGMA integrity_check } {ok}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that nothing goes wrong if an CONCURRENT transaction allocates a
|
||||
# page at the end of the file, frees it within the same transaction, and
|
||||
# then has to move the same page to avoid a conflict on COMMIT.
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 4.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(x);
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 4.$tn.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
DELETE FROM t1 WHERE rowid = 1;
|
||||
}
|
||||
|
||||
sql2 {
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
DELETE FROM t2 WHERE rowid IN (1, 2);
|
||||
}
|
||||
|
||||
sql1 COMMIT
|
||||
} {}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 5.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(x);
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
PRAGMA page_count;
|
||||
}
|
||||
} {wal 4}
|
||||
|
||||
do_test 5.$tn.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
PRAGMA page_count;
|
||||
}
|
||||
} {5}
|
||||
|
||||
do_test 5.$tn.3 {
|
||||
sql2 {
|
||||
DELETE FROM t1;
|
||||
PRAGMA freelist_count;
|
||||
PRAGMA page_count;
|
||||
}
|
||||
} {1 4}
|
||||
|
||||
do_test 5.$tn.4 { sql1 COMMIT } {}
|
||||
do_test 5.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 6.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
PRAGMA wal_checkpoint;
|
||||
}
|
||||
} {wal 0 5 5}
|
||||
|
||||
do_test 6.$tn.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 6.$tn.3 {
|
||||
sql2 {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
COMMIT;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 6.$tn.4 {
|
||||
list [catch { sql1 COMMIT } msg] $msg
|
||||
} {1 {database is locked}}
|
||||
do_test 6.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
|
||||
do_test 6.$tn.5 { sql3 { SELECT count(*) from t1 } } {3}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that if a corrupt wal-index-header is encountered when attempting
|
||||
# to commit a CONCURRENT transaction, the transaction is not committed
|
||||
# (or rolled back) and that SQLITE_BUSY_SNAPSHOT is returned to the user.
|
||||
#
|
||||
catch { db close }
|
||||
forcedelete test.db
|
||||
testvfs tvfs
|
||||
sqlite3 db test.db -vfs tvfs
|
||||
do_execsql_test 7.1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
BEGIN;
|
||||
CREATE TABLE t1(a, b, PRIMARY KEY(a));
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(3, 4);
|
||||
COMMIT;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(5, 6);
|
||||
INSERT INTO t1 VALUES(7, 8);
|
||||
SELECT * FROM t1;
|
||||
} {wal 1 2 3 4 5 6 7 8}
|
||||
|
||||
# Corrupt the wal-index header
|
||||
incr_tvfs_hdr test.db 11 1
|
||||
|
||||
do_catchsql_test 7.2.1 { COMMIT } {1 {database is locked}}
|
||||
do_test 7.2.2 { sqlite3_extended_errcode db } SQLITE_BUSY_SNAPSHOT
|
||||
|
||||
do_execsql_test 7.3.1 {
|
||||
SELECT * FROM t1;
|
||||
ROLLBACK;
|
||||
} {1 2 3 4 5 6 7 8}
|
||||
do_execsql_test 7.3.2 {
|
||||
SELECT * FROM t1;
|
||||
} {1 2 3 4}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that "PRAGMA integrity_check" works within a concurrent
|
||||
# transaction. Within a concurrent transaction, "PRAGMA integrity_check"
|
||||
# is unable to detect unused database pages, but can detect other types
|
||||
# of corruption.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 8.1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE kv(k INTEGER PRIMARY KEY, v UNIQUE);
|
||||
INSERT INTO kv VALUES(NULL, randomblob(750));
|
||||
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
|
||||
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
|
||||
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
|
||||
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
|
||||
INSERT INTO kv SELECT NULL, randomblob(750) FROM kv;
|
||||
DELETE FROM kv WHERE rowid%2;
|
||||
PRAGMA freelist_count;
|
||||
} {wal 34}
|
||||
do_execsql_test 8.2 { PRAGMA integrity_check } ok
|
||||
do_execsql_test 8.3 {
|
||||
BEGIN CONCURRENT;
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
do_execsql_test 8.4 {
|
||||
INSERT INTO kv VALUES(1100, 1100);
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
do_execsql_test 8.5 {
|
||||
COMMIT;
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that concurrent transactions do not allow foreign-key constraints
|
||||
# to be bypassed.
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 9.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE pp(i INTEGER PRIMARY KEY, j);
|
||||
CREATE TABLE cc(a, b REFERENCES pp);
|
||||
|
||||
WITH seq(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM seq WHERE i<100)
|
||||
INSERT INTO pp SELECT i, randomblob(1000) FROM seq;
|
||||
|
||||
PRAGMA foreign_keys = 1;
|
||||
}
|
||||
} {wal}
|
||||
|
||||
|
||||
do_test 9.$tn.2.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO cc VALUES(42, 42);
|
||||
}
|
||||
} {}
|
||||
do_test 9.$tn.2.2 {
|
||||
sql2 { DELETE FROM pp WHERE i=42 }
|
||||
list [catch { sql1 COMMIT } msg] $msg
|
||||
} {1 {database is locked}}
|
||||
do_test 9.$tn.2.3 {
|
||||
sql1 ROLLBACK
|
||||
} {}
|
||||
|
||||
do_test 9.$tn.3.1 {
|
||||
sql1 {
|
||||
PRAGMA foreign_keys = 0;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO cc VALUES(43, 43);
|
||||
}
|
||||
} {}
|
||||
do_test 9.$tn.3.2 {
|
||||
sql2 { DELETE FROM pp WHERE i=43 }
|
||||
list [catch { sql1 COMMIT } msg] $msg
|
||||
} {0 {}}
|
||||
|
||||
do_test 9.$tn.4.1 {
|
||||
sql1 {
|
||||
PRAGMA foreign_keys = on;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO cc VALUES(44, 44);
|
||||
}
|
||||
} {}
|
||||
do_test 9.$tn.4.2 {
|
||||
sql2 { DELETE FROM pp WHERE i=1 }
|
||||
list [catch { sql1 COMMIT } msg] $msg
|
||||
} {0 {}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that even if a SELECT statement appears before all writes within
|
||||
# a CONCURRENT transaction, the pages it reads are still considered when
|
||||
# considering whether or not the transaction may be committed.
|
||||
#
|
||||
do_multiclient_test tn {
|
||||
do_test 10.$tn.1.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a);
|
||||
CREATE TABLE t2(b);
|
||||
CREATE TABLE t3(c);
|
||||
INSERT INTO t1 VALUES(1), (2), (3);
|
||||
INSERT INTO t2 VALUES(1), (2), (3);
|
||||
INSERT INTO t3 VALUES(1), (2), (3);
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 10.$tn.1.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t1;
|
||||
INSERT INTO t2 VALUES(4);
|
||||
}
|
||||
} {1 2 3}
|
||||
|
||||
do_test 10.$tn.1.3 {
|
||||
sql2 { INSERT INTO t1 VALUES(4) }
|
||||
list [catch {sql1 COMMIT} msg] $msg
|
||||
} {1 {database is locked}}
|
||||
sql1 ROLLBACK
|
||||
|
||||
# In this case, because the "SELECT * FROM t1" is first stepped before
|
||||
# the "BEGIN CONCURRENT", the pages it reads are not recorded by the
|
||||
# pager object. And so the transaction can be committed. Technically
|
||||
# this behaviour (the effect of an ongoing SELECT on a BEGIN CONCURRENT
|
||||
# transacation) is undefined.
|
||||
#
|
||||
do_test 10.$tn.2.1 {
|
||||
code1 {
|
||||
set ::stmt [sqlite3_prepare db "SELECT * FROM t1" -1 dummy]
|
||||
sqlite3_step $::stmt
|
||||
}
|
||||
} {SQLITE_ROW}
|
||||
do_test 10.$tn.2.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES(4);
|
||||
}
|
||||
code1 {
|
||||
set res [list]
|
||||
lappend res [sqlite3_column_int $::stmt 0]
|
||||
while {[sqlite3_step $::stmt]=="SQLITE_ROW"} {
|
||||
lappend res [sqlite3_column_int $::stmt 0]
|
||||
}
|
||||
sqlite3_finalize $::stmt
|
||||
set res
|
||||
}
|
||||
} {1 2 3 4}
|
||||
do_test 10.$tn.2.3 {
|
||||
sql2 { INSERT INTO t1 VALUES(5) }
|
||||
sql1 COMMIT
|
||||
} {}
|
||||
|
||||
# More tests surrounding long-lived prepared statements and concurrent
|
||||
# transactions.
|
||||
do_test 10.$tn.3.1 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t1;
|
||||
COMMIT;
|
||||
}
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES(5);
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t1 VALUES(5);
|
||||
}
|
||||
sql1 COMMIT
|
||||
sql3 {
|
||||
SELECT * FROM t2;
|
||||
}
|
||||
} {1 2 3 4 5}
|
||||
do_test 10.$tn.3.2 {
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t1;
|
||||
ROLLBACK;
|
||||
}
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES(6);
|
||||
}
|
||||
sql2 {
|
||||
INSERT INTO t1 VALUES(6);
|
||||
}
|
||||
sql1 COMMIT
|
||||
sql3 { SELECT * FROM t2 }
|
||||
} {1 2 3 4 5 6}
|
||||
do_test 10.$tn.3.3 {
|
||||
sql1 { BEGIN CONCURRENT }
|
||||
code1 {
|
||||
set ::stmt [sqlite3_prepare db "SELECT * FROM t1" -1 dummy]
|
||||
sqlite3_step $::stmt
|
||||
}
|
||||
sql1 {
|
||||
INSERT INTO t2 VALUES(7);
|
||||
SELECT * FROM t3;
|
||||
ROLLBACK;
|
||||
BEGIN CONCURRENT;
|
||||
}
|
||||
sql2 { INSERT INTO t3 VALUES(5) }
|
||||
code1 { sqlite3_finalize $::stmt }
|
||||
sql1 {
|
||||
INSERT INTO t2 VALUES(8);
|
||||
COMMIT;
|
||||
}
|
||||
} {}
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 11.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a);
|
||||
}
|
||||
} {wal}
|
||||
|
||||
do_test 11.$tn.2 {
|
||||
code1 { sqlite3_wal_info db main }
|
||||
} {0 2}
|
||||
|
||||
do_test 11.$tn.3 {
|
||||
sql1 { INSERT INTO t1 VALUES(1) }
|
||||
code1 { sqlite3_wal_info db main }
|
||||
} {2 3}
|
||||
|
||||
do_test 11.$tn.4 {
|
||||
sql2 { INSERT INTO t1 VALUES(2) }
|
||||
code2 { sqlite3_wal_info db2 main }
|
||||
} {3 4}
|
||||
|
||||
do_test 11.$tn.5 {
|
||||
sql1 { PRAGMA wal_checkpoint }
|
||||
sql2 { INSERT INTO t1 VALUES(3) }
|
||||
code2 { sqlite3_wal_info db2 main }
|
||||
} {0 1}
|
||||
}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 12.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE tx(a INTEGER PRIMARY KEY, b);
|
||||
} {wal}
|
||||
do_test 12.1 {
|
||||
for {set i 0} {$i < 50} {incr i} {
|
||||
execsql {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO tx(b) VALUES( randomblob( 1200 ) );
|
||||
COMMIT;
|
||||
}
|
||||
}
|
||||
execsql { PRAGMA page_size }
|
||||
} {1024}
|
||||
do_execsql_test 12.2 {
|
||||
DELETE FROM tx;
|
||||
}
|
||||
do_test 12.3 {
|
||||
for {set i 0} {$i < 50} {incr i} {
|
||||
execsql {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO tx(b) VALUES( randomblob( 1200 ) );
|
||||
COMMIT;
|
||||
}
|
||||
}
|
||||
execsql { PRAGMA page_size }
|
||||
} {1024}
|
||||
do_execsql_test 12.4 {
|
||||
DELETE FROM tx;
|
||||
}
|
||||
do_test 12.5 {
|
||||
execsql { BEGIN CONCURRENT }
|
||||
for {set i 0} {$i < 5000} {incr i} {
|
||||
execsql {
|
||||
INSERT INTO tx(b) VALUES( randomblob( 1200 ) );
|
||||
}
|
||||
}
|
||||
execsql { COMMIT }
|
||||
execsql { PRAGMA page_size }
|
||||
} {1024}
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,231 @@
|
||||
# 2015 July 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Tests for transactions started with BEGIN CONCURRENT. The tests in this
|
||||
# file focus on testing that deferred page allocation works properly.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
set ::testprefix concurrent3
|
||||
|
||||
if {$AUTOVACUUM} { finish_test ; return }
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
db close
|
||||
sqlite3_shutdown
|
||||
test_sqlite3_log xLog
|
||||
proc xLog {error_code msg} {
|
||||
# puts "$error_code: $msg"
|
||||
# Enable the previous for debugging
|
||||
}
|
||||
reset_db
|
||||
|
||||
proc create_schema {} {
|
||||
db eval {
|
||||
PRAGMA journal_mode = wal;
|
||||
|
||||
CREATE TABLE t1(x, y);
|
||||
CREATE TABLE t2(x, y);
|
||||
CREATE TABLE t3(x, y);
|
||||
CREATE TABLE t4(x, y);
|
||||
|
||||
CREATE INDEX i1 ON t1(y, x);
|
||||
CREATE INDEX i2 ON t2(y, x);
|
||||
CREATE INDEX i3 ON t3(y, x);
|
||||
CREATE INDEX i4 ON t4(y, x);
|
||||
}
|
||||
}
|
||||
|
||||
proc do_sql_op {iTbl iOp} {
|
||||
set db "db$iTbl"
|
||||
|
||||
switch $iOp {
|
||||
"i" {
|
||||
set sql "
|
||||
WITH cnt(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<10)
|
||||
INSERT INTO t$iTbl SELECT randomblob(800), randomblob(800) FROM cnt;
|
||||
"
|
||||
}
|
||||
|
||||
"d" {
|
||||
set sql "
|
||||
DELETE FROM t$iTbl WHERE rowid IN (
|
||||
SELECT rowid FROM t$iTbl ORDER BY 1 ASC LIMIT 10
|
||||
)
|
||||
"
|
||||
}
|
||||
|
||||
"D" {
|
||||
set sql "
|
||||
DELETE FROM t$iTbl WHERE rowid IN (
|
||||
SELECT rowid FROM t$iTbl o WHERE (
|
||||
SELECT count(*) FROM t$iTbl i WHERE i.rowid<o.rowid
|
||||
) % 2
|
||||
)
|
||||
"
|
||||
}
|
||||
|
||||
"I" {
|
||||
set sql "
|
||||
INSERT INTO t$iTbl SELECT randomblob(800), randomblob(800) FROM t$iTbl;
|
||||
"
|
||||
}
|
||||
|
||||
default {
|
||||
error "bad iOp parameter: $iOp"
|
||||
}
|
||||
}
|
||||
|
||||
$db eval $sql
|
||||
}
|
||||
|
||||
|
||||
set DBLIST {db1 db2 db3 db4}
|
||||
|
||||
create_schema
|
||||
foreach {tn oplist} {
|
||||
1 {1i 2i 3i 4i}
|
||||
2 {1iii 2iii 3iii 4iii}
|
||||
3 {1d 2d 3d 4d}
|
||||
. -----------------------
|
||||
4 {1i}
|
||||
5 {1d 2i}
|
||||
. -----------------------
|
||||
6 {1iii 2iii 3iii 4iii}
|
||||
7 {1di 2id 3iii 4ddd}
|
||||
8 {1iii 2iii 3iii 4iii}
|
||||
9 {1D 2II}
|
||||
10 {1I 2D 3I 4D}
|
||||
11 {1III 3dddddd 4III}
|
||||
} {
|
||||
if {[string range $oplist 0 0]=="-"} {
|
||||
reset_db
|
||||
create_schema
|
||||
continue
|
||||
}
|
||||
foreach db $DBLIST { sqlite3 $db test.db }
|
||||
|
||||
do_test 1.$tn {
|
||||
foreach db $DBLIST { $db eval "BEGIN CONCURRENT" }
|
||||
|
||||
foreach op $oplist {
|
||||
set iTbl [string range $op 0 0]
|
||||
foreach char [split [string range $op 1 end] {}] {
|
||||
do_sql_op $iTbl $char
|
||||
}
|
||||
}
|
||||
|
||||
foreach db $DBLIST { $db eval "COMMIT" }
|
||||
db eval {PRAGMA integrity_check}
|
||||
} {ok}
|
||||
|
||||
foreach db $DBLIST {
|
||||
$db close
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
proc create_schema2 {} {
|
||||
db eval {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
|
||||
CREATE INDEX i1 ON t1(y);
|
||||
}
|
||||
}
|
||||
|
||||
proc randint {nMax} {
|
||||
db eval {SELECT abs(random() % $nMax)}
|
||||
}
|
||||
|
||||
proc do_sql_op2 {db iOp} {
|
||||
switch -- $iOp {
|
||||
i {
|
||||
# Insert 1 rows.
|
||||
set r [randint 1000000000]
|
||||
set ::rows($r) 1
|
||||
#puts "insert row $r"
|
||||
$db eval { INSERT OR IGNORE INTO t1 VALUES($r, randomblob(50)); }
|
||||
}
|
||||
|
||||
d {
|
||||
# Insert 1 row
|
||||
set keys [array names ::rows]
|
||||
set r [randint [llength $keys]]
|
||||
set rowid [lindex $keys $r]
|
||||
$db eval { DELETE FROM t1 WHERE x=$rowid }
|
||||
unset ::rows($rowid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach {tn nRepeat oplist} {
|
||||
- - ----------------------------
|
||||
1 100 { 1iiiiiiiiii }
|
||||
2 100 { 1i 2d }
|
||||
3 100 { 1d 2i }
|
||||
4 50 { 1d 2i 3d }
|
||||
5 500 { 1i 2i 3i 4i }
|
||||
6 500 { 1i 2d 3d 4d }
|
||||
} {
|
||||
if {[string range $oplist 0 0]=="-"} {
|
||||
array unset rows
|
||||
reset_db
|
||||
create_schema2
|
||||
continue
|
||||
}
|
||||
|
||||
foreach db $DBLIST {
|
||||
sqlite3 $db test.db
|
||||
set stats($db,0) 0
|
||||
set stats($db,1) 0
|
||||
}
|
||||
array unset used
|
||||
|
||||
do_test 2.$tn {
|
||||
|
||||
for {set i 0} {$i < $nRepeat} {incr i} {
|
||||
foreach db $DBLIST { $db eval "BEGIN CONCURRENT" }
|
||||
|
||||
foreach op $oplist {
|
||||
set iDb [string range $op 0 0]
|
||||
set used(db$iDb) 1
|
||||
foreach char [split [string range $op 1 end] {}] {
|
||||
do_sql_op2 "db$iDb" $char
|
||||
}
|
||||
}
|
||||
|
||||
foreach db $DBLIST {
|
||||
set rc [catch { $db eval COMMIT } msg]
|
||||
if {$rc} { $db eval ROLLBACK }
|
||||
incr stats($db,$rc)
|
||||
}
|
||||
set res [db eval {PRAGMA integrity_check}]
|
||||
if {$res != "ok"} { puts "after $db $rc: $res" ; after 1000000 }
|
||||
}
|
||||
} {}
|
||||
|
||||
foreach db $DBLIST {
|
||||
$db close
|
||||
}
|
||||
# foreach k [lsort [array names used]] {
|
||||
# puts "$k: $stats($k,0) committed, $stats($k,1) rolled back"
|
||||
# }
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,194 @@
|
||||
# 2017 May 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Miscellaneous tests for transactions started with BEGIN CONCURRENT.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
set ::testprefix concurrent4
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x PRIMARY KEY, y UNIQUE);
|
||||
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM s;
|
||||
DELETE FROM t1 WHERE rowid<2;
|
||||
} {wal}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1(rowid, x, y) VALUES(1000, randomblob(3000), randomblob(3000));
|
||||
SAVEPOINT abc;
|
||||
DELETE FROM t1 WHERE rowid = 1000;
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 { ROLLBACK TO abc }
|
||||
do_execsql_test 1.3 { COMMIT }
|
||||
do_execsql_test 1.4 { PRAGMA integrity_check } {ok}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 2.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(a, b);
|
||||
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM s;
|
||||
|
||||
CREATE TABLE t2(a, b);
|
||||
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t2 SELECT randomblob(400), randomblob(400) FROM s;
|
||||
}
|
||||
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(3000), randomblob(3000));
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.2 {
|
||||
sql2 {
|
||||
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10)
|
||||
INSERT INTO t2 SELECT randomblob(400), randomblob(400) FROM s;
|
||||
}
|
||||
sql2 {
|
||||
DELETE FROM t2 WHERE rowid<10;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.3 {
|
||||
sql1 {
|
||||
COMMIT;
|
||||
PRAGMA integrity_check;
|
||||
}
|
||||
} {ok}
|
||||
do_test 2.$tn.4 {
|
||||
sql2 {
|
||||
PRAGMA integrity_check;
|
||||
}
|
||||
} {ok}
|
||||
}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 3.1 {
|
||||
PRAGMA page_size = 1024;
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t2(x);
|
||||
INSERT INTO t2 VALUES(randomblob(5000));
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO t1 VALUES(25, randomblob(104));
|
||||
DELETE FROM t2;
|
||||
} {wal}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
BEGIN CONCURRENT;
|
||||
REPLACE INTO t1 VALUES(25, randomblob(1117));
|
||||
COMMIT;
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the effect of BEGIN CONCURRENT transactions that consist entirely
|
||||
# of read-only statements.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 4.0 {
|
||||
PRAGMA page_size = 1024;
|
||||
PRAGMA journal_mode = wal;
|
||||
|
||||
CREATE TABLE t4(a, b);
|
||||
INSERT INTO t4 VALUES(1, 2);
|
||||
INSERT INTO t4 VALUES(3, 4);
|
||||
} {wal}
|
||||
|
||||
sqlite3 db2 test.db
|
||||
do_test 4.1.1 {
|
||||
db eval {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t4 VALUES(5, 6);
|
||||
}
|
||||
|
||||
db2 eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t4;
|
||||
ROLLBACK;
|
||||
}
|
||||
} {1 2 3 4}
|
||||
|
||||
do_test 4.1.2 {
|
||||
db eval { COMMIT }
|
||||
db2 eval { SELECT * FROM t4 }
|
||||
} {1 2 3 4 5 6}
|
||||
|
||||
do_test 4.2.1 {
|
||||
db eval {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t4 VALUES(7, 8);
|
||||
}
|
||||
|
||||
db2 eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t4;
|
||||
COMMIT;
|
||||
}
|
||||
} {1 2 3 4 5 6}
|
||||
|
||||
do_test 4.2.2 {
|
||||
db eval { COMMIT }
|
||||
db2 eval { SELECT * FROM t4 }
|
||||
} {1 2 3 4 5 6 7 8}
|
||||
|
||||
do_test 4.3 {
|
||||
db2 eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t4;
|
||||
}
|
||||
|
||||
db eval {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t4 VALUES(9, 10);
|
||||
COMMIT;
|
||||
}
|
||||
db2 eval {
|
||||
SELECT * FROM t4;
|
||||
COMMIT;
|
||||
}
|
||||
} {1 2 3 4 5 6 7 8}
|
||||
|
||||
set sz [file size test.db-wal]
|
||||
do_test 4.4.1 {
|
||||
db eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t4;
|
||||
SELECT * FROM sqlite_master;
|
||||
}
|
||||
|
||||
db eval COMMIT
|
||||
file size test.db-wal
|
||||
} $sz
|
||||
do_test 4.4.2 {
|
||||
db eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT * FROM t4;
|
||||
SELECT * FROM sqlite_master;
|
||||
ROLLBACK;
|
||||
}
|
||||
file size test.db-wal
|
||||
} $sz
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# 2017 May 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
set ::testprefix concurrent5
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
db close
|
||||
sqlite3_shutdown
|
||||
test_sqlite3_log [list lappend ::log]
|
||||
set ::log [list]
|
||||
|
||||
sqlite3 db test.db
|
||||
|
||||
proc do_test_conflict_msg {tn msg} {
|
||||
set msg "cannot commit CONCURRENT transaction - [string trim $msg]"
|
||||
uplevel [list do_test $tn {lindex $::log end} $msg]
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x, y);
|
||||
CREATE TABLE t2(c);
|
||||
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t1 SELECT randomblob(400), randomblob(400) FROM s;
|
||||
} {wal}
|
||||
|
||||
sqlite3 db2 test.db
|
||||
|
||||
do_test 1.1.1 {
|
||||
set ::log [list]
|
||||
db2 eval {
|
||||
BEGIN CONCURRENT;
|
||||
SELECT count(*) FROM t1;
|
||||
INSERT INTO t2 VALUES(10);
|
||||
}
|
||||
|
||||
db eval {
|
||||
INSERT INTO t1 VALUES(randomblob(200), randomblob(200));
|
||||
}
|
||||
|
||||
catchsql COMMIT db2
|
||||
} {1 {database is locked}}
|
||||
do_test_conflict_msg 1.1.2 {
|
||||
conflict at page 2 (read-only page; part of db table t1; content=0500000063021100...)
|
||||
}
|
||||
|
||||
do_test 1.2.1 {
|
||||
set ::log [list]
|
||||
db2 eval {
|
||||
ROLLBACK;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(11, 12);
|
||||
}
|
||||
|
||||
db eval {
|
||||
INSERT INTO t1 VALUES(12, 11);
|
||||
}
|
||||
|
||||
catchsql COMMIT db2
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test_conflict_msg 1.2.2 {
|
||||
conflict at page 105 (read/write page; part of db table t1; content=0D00000002026100...)
|
||||
}
|
||||
|
||||
do_test 1.3.1 {
|
||||
set ::log [list]
|
||||
db2 eval {
|
||||
ROLLBACK;
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t2 VALUES('x');
|
||||
}
|
||||
|
||||
db eval {
|
||||
INSERT INTO t2 VALUES('y');
|
||||
}
|
||||
|
||||
catchsql COMMIT db2
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test_conflict_msg 1.3.2 {
|
||||
conflict at page 3 (read/write page; part of db table t2; content=0D0000000103FB00...)
|
||||
}
|
||||
|
||||
do_test 1.4.1 {
|
||||
set ::log [list]
|
||||
|
||||
execsql {
|
||||
ROLLBACK;
|
||||
CREATE TABLE t3(a INTEGER PRIMARY KEY, b INTEGER);
|
||||
CREATE INDEX i3 ON t3(b);
|
||||
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<5000
|
||||
) INSERT INTO t3 SELECT i, i FROM s;
|
||||
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t3 VALUES(0, 5001);
|
||||
} db2
|
||||
|
||||
execsql { INSERT INTO t3 VALUES(NULL, 5002) } db
|
||||
catchsql COMMIT db2
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_test_conflict_msg 1.3.2 {
|
||||
conflict at page 211 (read/write page; part of db index t3.i3; content=0A0310006300D800...)
|
||||
}
|
||||
|
||||
db close
|
||||
db2 close
|
||||
sqlite3_shutdown
|
||||
test_sqlite3_log
|
||||
sqlite3_initialize
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# 2017 May 26
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
set ::testprefix concurrent6
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
sqlite3 db2 test.db
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA page_size = 1024;
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(x);
|
||||
CREATE TABLE t3(x);
|
||||
CREATE TABLE t4(x);
|
||||
|
||||
INSERT INTO t1 VALUES(zeroblob(1500));
|
||||
} {wal}
|
||||
|
||||
do_execsql_test -db db2 1.1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t3 VALUES(zeroblob(4000));
|
||||
DELETE FROM t1;
|
||||
}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t2 SELECT zeroblob(1000) FROM s;
|
||||
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100)
|
||||
INSERT INTO t4 SELECT zeroblob(1000) FROM s;
|
||||
|
||||
DELETE FROM t4;
|
||||
}
|
||||
|
||||
do_execsql_test -db db2 1.3 {
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# 2018 Jan 5
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set ::testprefix concurrent7
|
||||
|
||||
sqlite3 db2 test.db
|
||||
|
||||
do_execsql_test 1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(x);
|
||||
} {wal}
|
||||
|
||||
do_execsql_test -db db2 2 {
|
||||
SELECT * FROM t1;
|
||||
}
|
||||
|
||||
do_execsql_test 3 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
INSERT INTO t1 VALUES(randomblob(1500));
|
||||
DELETE FROM t1 WHERE rowid = 1;
|
||||
}
|
||||
|
||||
do_execsql_test -db db2 4 {
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
INSERT INTO t2 VALUES(randomblob(1500));
|
||||
DELETE FROM t2 WHERE rowid IN (1, 2);
|
||||
}
|
||||
|
||||
do_execsql_test 5 {
|
||||
COMMIT;
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ db close
|
||||
forcecopy test.db test.db-template
|
||||
|
||||
set unreadable_version 02
|
||||
ifcapable wal { set unreadable_version 03 }
|
||||
ifcapable wal { set unreadable_version 04 }
|
||||
do_test corruptA-2.1 {
|
||||
forcecopy test.db-template test.db
|
||||
hexio_write test.db 19 $unreadable_version ;# the read format number
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
# 2014-08-24
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library.
|
||||
# The focus of this script is testing details of the SQL language parser.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
do_catchsql_test parser1-1.1 {
|
||||
CREATE TABLE t1(
|
||||
a TEXT PRIMARY KEY,
|
||||
b TEXT,
|
||||
FOREIGN KEY(b COLLATE nocase DESC) REFERENCES t1(a COLLATE binary ASC)
|
||||
);
|
||||
} {1 {syntax error after column name "b"}}
|
||||
|
||||
|
||||
# Verify that a legacy schema in the sqlite_master file is allowed to have
|
||||
# COLLATE, ASC, and DESC keywords on the id list of a FK constraint, and that
|
||||
# those keywords are silently ignored.
|
||||
#
|
||||
sqlite3_db_config db DEFENSIVE 0
|
||||
do_execsql_test parser1-1.2 {
|
||||
CREATE TABLE t1(
|
||||
a TEXT PRIMARY KEY,
|
||||
b TEXT,
|
||||
FOREIGN KEY(b) REFERENCES t1(a)
|
||||
);
|
||||
INSERT INTO t1 VALUES('abc',NULL),('xyz','abc');
|
||||
PRAGMA writable_schema=on;
|
||||
UPDATE sqlite_master SET sql='CREATE TABLE t1(
|
||||
a TEXT PRIMARY KEY,
|
||||
b TEXT,
|
||||
FOREIGN KEY(b COLLATE nocase) REFERENCES t1(a)
|
||||
)' WHERE name='t1';
|
||||
SELECT name FROM sqlite_master WHERE sql LIKE '%collate%';
|
||||
} {t1}
|
||||
sqlite3 db2 test.db
|
||||
do_test parser1-1.3 {
|
||||
sqlite3 db2 test.db
|
||||
db2 eval {SELECT * FROM t1 ORDER BY 1}
|
||||
} {abc {} xyz abc}
|
||||
db2 close
|
||||
|
||||
do_execsql_test parser1-1.4 {
|
||||
UPDATE sqlite_master SET sql='CREATE TABLE t1(
|
||||
a TEXT PRIMARY KEY,
|
||||
b TEXT,
|
||||
FOREIGN KEY(b ASC) REFERENCES t1(a)
|
||||
)' WHERE name='t1';
|
||||
SELECT name FROM sqlite_master WHERE sql LIKE '%ASC%';
|
||||
} {t1}
|
||||
sqlite3 db2 test.db
|
||||
do_test parser1-1.5 {
|
||||
sqlite3 db2 test.db
|
||||
db2 eval {SELECT * FROM t1 ORDER BY 1}
|
||||
} {abc {} xyz abc}
|
||||
db2 close
|
||||
|
||||
do_catchsql_test parser1-2.1 {
|
||||
WITH RECURSIVE
|
||||
c(x COLLATE binary) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<5)
|
||||
SELECT x FROM c;
|
||||
} {1 {syntax error after column name "x"}}
|
||||
do_catchsql_test parser1-2.2 {
|
||||
WITH RECURSIVE
|
||||
c(x ASC) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<5)
|
||||
SELECT x FROM c;
|
||||
} {1 {syntax error after column name "x"}}
|
||||
|
||||
# Verify that the comma between multiple table constraints is
|
||||
# optional.
|
||||
#
|
||||
# The missing comma is technically a syntax error. But we have to support
|
||||
# it because there might be legacy databases that omit the commas in their
|
||||
# sqlite_master tables.
|
||||
#
|
||||
do_execsql_test parser1-3.1 {
|
||||
CREATE TABLE t300(id INTEGER PRIMARY KEY);
|
||||
CREATE TABLE t301(
|
||||
id INTEGER PRIMARY KEY,
|
||||
c1 INTEGER NOT NULL,
|
||||
c2 INTEGER NOT NULL,
|
||||
c3 BOOLEAN NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY(c1) REFERENCES t300(id) ON DELETE CASCADE ON UPDATE RESTRICT
|
||||
/* no comma */
|
||||
FOREIGN KEY(c2) REFERENCES t300(id) ON DELETE CASCADE ON UPDATE RESTRICT
|
||||
/* no comma */
|
||||
UNIQUE(c1, c2)
|
||||
);
|
||||
PRAGMA foreign_key_list(t301);
|
||||
} {0 0 t300 c2 id RESTRICT CASCADE NONE 1 0 t300 c1 id RESTRICT CASCADE NONE}
|
||||
|
||||
finish_test
|
||||
+36
-9
@@ -430,18 +430,28 @@ lappend ::testsuitelist xxx
|
||||
test_suite "coverage-wal" -description {
|
||||
Coverage tests for file wal.c.
|
||||
} -files {
|
||||
wal.test wal2.test wal3.test wal4.test wal5.test
|
||||
wal64k.test wal6.test wal7.test wal8.test wal9.test
|
||||
walbak.test walbig.test walblock.test walcksum.test walcrash2.test
|
||||
walcrash3.test walcrash4.test walcrash.test walfault.test walhook.test
|
||||
walmode.test walnoshm.test waloverwrite.test walpersist.test
|
||||
walprotocol2.test walprotocol.test walro2.test walrofault.test
|
||||
walro.test walshared.test walslow.test walvfs.test
|
||||
walfault2.test
|
||||
nockpt.test
|
||||
wal2big.test wal2recover.test wal2rewrite.test
|
||||
wal2simple.test wal2snapshot.test wal2.test
|
||||
wal3.test wal4.test wal5.test
|
||||
wal64k.test wal6.test wal7.test wal8.test wal9.test
|
||||
walbak.test walbig.test walblock.test walcksum.test
|
||||
walfault.test walhook.test walmode.test walnoshm.test
|
||||
waloverwrite.test walpersist.test walprotocol2.test
|
||||
walprotocol.test walro2.test walrofault.test walro.test
|
||||
walshared.test walslow.test wal.test
|
||||
wal2savepoint.test wal2lock.test wal2recover2.test
|
||||
|
||||
wal2concurrent.test
|
||||
concurrent.test concurrent2.test concurrent3.test
|
||||
concurrent4.test concurrent5.test concurrent6.test
|
||||
concurrent7.test
|
||||
concfault.test concfault2.test
|
||||
|
||||
walvfs.test walfault2.test nockpt.test
|
||||
snapshot2.test snapshot3.test snapshot4.test
|
||||
snapshot_fault.test snapshot.test snapshot_up.test
|
||||
walcrash2.test walcrash3.test walcrash4.test walcrash.test
|
||||
wal2fault.test
|
||||
}
|
||||
|
||||
test_suite "coverage-pager" -description {
|
||||
@@ -999,6 +1009,23 @@ test_suite "wal" -description {
|
||||
fts3c.test fts3d.test fts3e.test fts3query.test
|
||||
}
|
||||
|
||||
test_suite "wal2" -description {
|
||||
Run tests with journal_mode=WAL2
|
||||
} -initialize {
|
||||
set ::G(savepoint6_iterations) 100
|
||||
} -shutdown {
|
||||
unset -nocomplain ::G(savepoint6_iterations)
|
||||
} -files {
|
||||
savepoint.test savepoint2.test savepoint6.test
|
||||
trans.test avtrans.test
|
||||
|
||||
fts3aa.test fts3ab.test fts3ac.test fts3ad.test
|
||||
fts3ae.test fts3af.test fts3ag.test fts3ah.test
|
||||
fts3ai.test fts3aj.test fts3ak.test fts3al.test
|
||||
fts3am.test fts3an.test fts3ao.test fts3b.test
|
||||
fts3c.test fts3d.test fts3e.test fts3query.test
|
||||
}
|
||||
|
||||
test_suite "rtree" -description {
|
||||
All R-tree related tests. Provides coverage of source file rtree.c.
|
||||
} -files [glob -nocomplain $::testdir/../ext/rtree/*.test]
|
||||
|
||||
+3
-3
@@ -41,7 +41,7 @@ do_test rdonly-1.1.1 {
|
||||
sqlite3_db_readonly db main
|
||||
} {0}
|
||||
|
||||
# Changes the write version from 1 to 3. Verify that the database
|
||||
# Changes the write version from 1 to 4. Verify that the database
|
||||
# can be read but not written.
|
||||
#
|
||||
do_test rdonly-1.2 {
|
||||
@@ -49,7 +49,7 @@ do_test rdonly-1.2 {
|
||||
hexio_get_int [hexio_read test.db 18 1]
|
||||
} 1
|
||||
do_test rdonly-1.3 {
|
||||
hexio_write test.db 18 03
|
||||
hexio_write test.db 18 04
|
||||
sqlite3 db test.db
|
||||
execsql {
|
||||
SELECT * FROM t1;
|
||||
@@ -83,7 +83,7 @@ do_test rdonly-1.5 {
|
||||
# the database is read-only until after it is locked.
|
||||
#
|
||||
set ro_version 02
|
||||
ifcapable wal { set ro_version 03 }
|
||||
ifcapable wal { set ro_version 04 }
|
||||
do_test rdonly-1.6 {
|
||||
hexio_write test.db 18 $ro_version ; # write-version
|
||||
hexio_write test.db 24 11223344 ; # change-counter
|
||||
|
||||
+3
-1
@@ -28,6 +28,7 @@ do_test savepoint-1.1 {
|
||||
RELEASE sp1;
|
||||
}
|
||||
} {}
|
||||
wal_check_journal_mode savepoint-1.1
|
||||
do_test savepoint-1.2 {
|
||||
execsql {
|
||||
SAVEPOINT sp1;
|
||||
@@ -805,7 +806,8 @@ do_test savepoint-11.6 {
|
||||
integrity_check savepoint-11.7
|
||||
do_test savepoint-11.8 {
|
||||
execsql { ROLLBACK }
|
||||
execsql { PRAGMA wal_checkpoint }
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
file size test.db
|
||||
} {8192}
|
||||
|
||||
|
||||
+14
-3
@@ -15,6 +15,10 @@ set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
proc sql {zSql} {
|
||||
if {0 && $::debug_op} {
|
||||
puts stderr "$zSql ;"
|
||||
flush stderr
|
||||
}
|
||||
uplevel db eval [list $zSql]
|
||||
#puts stderr "$zSql ;"
|
||||
}
|
||||
@@ -67,11 +71,13 @@ proc x_to_y {x} {
|
||||
# delete_rows XVALUES
|
||||
#
|
||||
proc savepoint {zName} {
|
||||
if {$::debug_op} { puts stderr "savepoint $zName" ; flush stderr }
|
||||
catch { sql "SAVEPOINT $zName" }
|
||||
lappend ::lSavepoint [list $zName [array get ::aEntry]]
|
||||
}
|
||||
|
||||
proc rollback {zName} {
|
||||
if {$::debug_op} { puts stderr "rollback $zName" ; flush stderr }
|
||||
catch { sql "ROLLBACK TO $zName" }
|
||||
for {set i [expr {[llength $::lSavepoint]-1}]} {$i>=0} {incr i -1} {
|
||||
set zSavepoint [lindex $::lSavepoint $i 0]
|
||||
@@ -89,6 +95,7 @@ proc rollback {zName} {
|
||||
}
|
||||
|
||||
proc release {zName} {
|
||||
if {$::debug_op} { puts stderr "release $zName" ; flush stderr }
|
||||
catch { sql "RELEASE $zName" }
|
||||
for {set i [expr {[llength $::lSavepoint]-1}]} {$i>=0} {incr i -1} {
|
||||
set zSavepoint [lindex $::lSavepoint $i 0]
|
||||
@@ -104,6 +111,7 @@ proc release {zName} {
|
||||
}
|
||||
|
||||
proc insert_rows {lX} {
|
||||
if {$::debug_op} { puts stderr "insert_rows $lX" ; flush stderr }
|
||||
foreach x $lX {
|
||||
set y [x_to_y $x]
|
||||
|
||||
@@ -116,6 +124,7 @@ proc insert_rows {lX} {
|
||||
}
|
||||
|
||||
proc delete_rows {lX} {
|
||||
if {$::debug_op} { puts stderr "delete_rows $lX" ; flush stderr }
|
||||
foreach x $lX {
|
||||
# Update database [db]
|
||||
sql "DELETE FROM t1 WHERE x = $x"
|
||||
@@ -164,6 +173,11 @@ proc random_integers {nRes nRange} {
|
||||
}
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
set ::debug_op 0
|
||||
proc debug_ops {} {
|
||||
set ::debug_op 1
|
||||
}
|
||||
|
||||
proc database_op {} {
|
||||
set i [expr int(rand()*2)]
|
||||
if {$i==0} {
|
||||
@@ -185,9 +199,6 @@ proc savepoint_op {} {
|
||||
set C [lindex $cmds [expr int(rand()*6)]]
|
||||
set N [lindex $names [expr int(rand()*5)]]
|
||||
|
||||
#puts stderr " $C $N ; "
|
||||
#flush stderr
|
||||
|
||||
$C $N
|
||||
return ok
|
||||
}
|
||||
|
||||
+20
-4
@@ -591,6 +591,7 @@ proc reset_db {} {
|
||||
forcedelete test.db
|
||||
forcedelete test.db-journal
|
||||
forcedelete test.db-wal
|
||||
forcedelete test.db-wal2
|
||||
sqlite3 db ./test.db
|
||||
set ::DB [sqlite3_connection_pointer db]
|
||||
if {[info exists ::SETUP_SQL]} {
|
||||
@@ -2140,17 +2141,32 @@ proc drop_all_indexes {{db db}} {
|
||||
# Returns true if this test should be run in WAL mode. False otherwise.
|
||||
#
|
||||
proc wal_is_wal_mode {} {
|
||||
expr {[permutation] eq "wal"}
|
||||
if {[permutation] eq "wal"} { return 1 }
|
||||
if {[permutation] eq "wal2"} { return 2 }
|
||||
return 0
|
||||
}
|
||||
proc wal_set_journal_mode {{db db}} {
|
||||
if { [wal_is_wal_mode] } {
|
||||
$db eval "PRAGMA journal_mode = WAL"
|
||||
switch -- [wal_is_wal_mode] {
|
||||
0 {
|
||||
}
|
||||
|
||||
1 {
|
||||
$db eval "PRAGMA journal_mode = WAL"
|
||||
}
|
||||
|
||||
2 {
|
||||
$db eval "PRAGMA journal_mode = WAL2"
|
||||
}
|
||||
}
|
||||
}
|
||||
proc wal_check_journal_mode {testname {db db}} {
|
||||
if { [wal_is_wal_mode] } {
|
||||
$db eval { SELECT * FROM sqlite_master }
|
||||
do_test $testname [list $db eval "PRAGMA main.journal_mode"] {wal}
|
||||
set expected "wal"
|
||||
if {[wal_is_wal_mode]==2} {
|
||||
set expected "wal2"
|
||||
}
|
||||
do_test $testname [list $db eval "PRAGMA main.journal_mode"] $expected
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-866
@@ -32,885 +32,20 @@
|
||||
** The exit status is non-zero if any test fails.
|
||||
*/
|
||||
|
||||
/*
|
||||
** The "Set Error Line" macro.
|
||||
*/
|
||||
#define SEL(e) ((e)->iLine = ((e)->rc ? (e)->iLine : __LINE__))
|
||||
|
||||
/* Database functions */
|
||||
#define opendb(w,x,y,z) (SEL(w), opendb_x(w,x,y,z))
|
||||
#define closedb(y,z) (SEL(y), closedb_x(y,z))
|
||||
|
||||
/* Functions to execute SQL */
|
||||
#define sql_script(x,y,z) (SEL(x), sql_script_x(x,y,z))
|
||||
#define integrity_check(x,y) (SEL(x), integrity_check_x(x,y))
|
||||
#define execsql_i64(x,y,...) (SEL(x), execsql_i64_x(x,y,__VA_ARGS__))
|
||||
#define execsql_text(x,y,z,...) (SEL(x), execsql_text_x(x,y,z,__VA_ARGS__))
|
||||
#define execsql(x,y,...) (SEL(x), (void)execsql_i64_x(x,y,__VA_ARGS__))
|
||||
#define sql_script_printf(x,y,z,...) ( \
|
||||
SEL(x), sql_script_printf_x(x,y,z,__VA_ARGS__) \
|
||||
)
|
||||
|
||||
/* Thread functions */
|
||||
#define launch_thread(w,x,y,z) (SEL(w), launch_thread_x(w,x,y,z))
|
||||
#define join_all_threads(y,z) (SEL(y), join_all_threads_x(y,z))
|
||||
|
||||
/* Timer functions */
|
||||
#define setstoptime(y,z) (SEL(y), setstoptime_x(y,z))
|
||||
#define timetostop(z) (SEL(z), timetostop_x(z))
|
||||
|
||||
/* Report/clear errors. */
|
||||
#define test_error(z, ...) test_error_x(z, sqlite3_mprintf(__VA_ARGS__))
|
||||
#define clear_error(y,z) clear_error_x(y, z)
|
||||
|
||||
/* File-system operations */
|
||||
#define filesize(y,z) (SEL(y), filesize_x(y,z))
|
||||
#define filecopy(x,y,z) (SEL(x), filecopy_x(x,y,z))
|
||||
|
||||
#define PTR2INT(x) ((int)((intptr_t)x))
|
||||
#define INT2PTR(x) ((void*)((intptr_t)x))
|
||||
|
||||
/*
|
||||
** End of test code/infrastructure interface macros.
|
||||
*************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "test_multiplex.h"
|
||||
#include "tt3_core.c"
|
||||
|
||||
/* Required to link test_multiplex.c */
|
||||
#ifndef SQLITE_OMIT_WSD
|
||||
int sqlite3PendingByte = 0x40000000;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This code implements the MD5 message-digest algorithm.
|
||||
* The algorithm is due to Ron Rivest. This code was
|
||||
* written by Colin Plumb in 1993, no copyright is claimed.
|
||||
* This code is in the public domain; do with it what you wish.
|
||||
*
|
||||
* Equivalent code is available from RSA Data Security, Inc.
|
||||
* This code has been tested against that, and is equivalent,
|
||||
* except that you don't need to include two pages of legalese
|
||||
* with every copy.
|
||||
*
|
||||
* To compute the message digest of a chunk of bytes, declare an
|
||||
* MD5Context structure, pass it to MD5Init, call MD5Update as
|
||||
* needed on buffers full of bytes, and then call MD5Final, which
|
||||
* will fill a supplied 16-byte array with the digest.
|
||||
*/
|
||||
|
||||
/*
|
||||
* If compiled on a machine that doesn't have a 32-bit integer,
|
||||
* you just set "uint32" to the appropriate datatype for an
|
||||
* unsigned 32-bit integer. For example:
|
||||
*
|
||||
* cc -Duint32='unsigned long' md5.c
|
||||
*
|
||||
*/
|
||||
#ifndef uint32
|
||||
# define uint32 unsigned int
|
||||
#endif
|
||||
|
||||
struct MD5Context {
|
||||
int isInit;
|
||||
uint32 buf[4];
|
||||
uint32 bits[2];
|
||||
union {
|
||||
unsigned char in[64];
|
||||
uint32 in32[16];
|
||||
} u;
|
||||
};
|
||||
typedef struct MD5Context MD5Context;
|
||||
|
||||
/*
|
||||
* Note: this code is harmless on little-endian machines.
|
||||
*/
|
||||
static void byteReverse (unsigned char *buf, unsigned longs){
|
||||
uint32 t;
|
||||
do {
|
||||
t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 |
|
||||
((unsigned)buf[1]<<8 | buf[0]);
|
||||
*(uint32 *)buf = t;
|
||||
buf += 4;
|
||||
} while (--longs);
|
||||
}
|
||||
/* The four core functions - F1 is optimized somewhat */
|
||||
|
||||
/* #define F1(x, y, z) (x & y | ~x & z) */
|
||||
#define F1(x, y, z) (z ^ (x & (y ^ z)))
|
||||
#define F2(x, y, z) F1(z, x, y)
|
||||
#define F3(x, y, z) (x ^ y ^ z)
|
||||
#define F4(x, y, z) (y ^ (x | ~z))
|
||||
|
||||
/* This is the central step in the MD5 algorithm. */
|
||||
#define MD5STEP(f, w, x, y, z, data, s) \
|
||||
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
|
||||
|
||||
/*
|
||||
* The core of the MD5 algorithm, this alters an existing MD5 hash to
|
||||
* reflect the addition of 16 longwords of new data. MD5Update blocks
|
||||
* the data and converts bytes into longwords for this routine.
|
||||
*/
|
||||
static void MD5Transform(uint32 buf[4], const uint32 in[16]){
|
||||
register uint32 a, b, c, d;
|
||||
|
||||
a = buf[0];
|
||||
b = buf[1];
|
||||
c = buf[2];
|
||||
d = buf[3];
|
||||
|
||||
MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
|
||||
|
||||
MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
|
||||
|
||||
MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
|
||||
|
||||
MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
|
||||
|
||||
buf[0] += a;
|
||||
buf[1] += b;
|
||||
buf[2] += c;
|
||||
buf[3] += d;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
|
||||
* initialization constants.
|
||||
*/
|
||||
static void MD5Init(MD5Context *ctx){
|
||||
ctx->isInit = 1;
|
||||
ctx->buf[0] = 0x67452301;
|
||||
ctx->buf[1] = 0xefcdab89;
|
||||
ctx->buf[2] = 0x98badcfe;
|
||||
ctx->buf[3] = 0x10325476;
|
||||
ctx->bits[0] = 0;
|
||||
ctx->bits[1] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update context to reflect the concatenation of another buffer full
|
||||
* of bytes.
|
||||
*/
|
||||
static
|
||||
void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len){
|
||||
uint32 t;
|
||||
|
||||
/* Update bitcount */
|
||||
|
||||
t = ctx->bits[0];
|
||||
if ((ctx->bits[0] = t + ((uint32)len << 3)) < t)
|
||||
ctx->bits[1]++; /* Carry from low to high */
|
||||
ctx->bits[1] += len >> 29;
|
||||
|
||||
t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
|
||||
|
||||
/* Handle any leading odd-sized chunks */
|
||||
|
||||
if ( t ) {
|
||||
unsigned char *p = (unsigned char *)ctx->u.in + t;
|
||||
|
||||
t = 64-t;
|
||||
if (len < t) {
|
||||
memcpy(p, buf, len);
|
||||
return;
|
||||
}
|
||||
memcpy(p, buf, t);
|
||||
byteReverse(ctx->u.in, 16);
|
||||
MD5Transform(ctx->buf, (uint32 *)ctx->u.in);
|
||||
buf += t;
|
||||
len -= t;
|
||||
}
|
||||
|
||||
/* Process data in 64-byte chunks */
|
||||
|
||||
while (len >= 64) {
|
||||
memcpy(ctx->u.in, buf, 64);
|
||||
byteReverse(ctx->u.in, 16);
|
||||
MD5Transform(ctx->buf, (uint32 *)ctx->u.in);
|
||||
buf += 64;
|
||||
len -= 64;
|
||||
}
|
||||
|
||||
/* Handle any remaining bytes of data. */
|
||||
|
||||
memcpy(ctx->u.in, buf, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Final wrapup - pad to 64-byte boundary with the bit pattern
|
||||
* 1 0* (64-bit count of bits processed, MSB-first)
|
||||
*/
|
||||
static void MD5Final(unsigned char digest[16], MD5Context *ctx){
|
||||
unsigned count;
|
||||
unsigned char *p;
|
||||
|
||||
/* Compute number of bytes mod 64 */
|
||||
count = (ctx->bits[0] >> 3) & 0x3F;
|
||||
|
||||
/* Set the first char of padding to 0x80. This is safe since there is
|
||||
always at least one byte free */
|
||||
p = ctx->u.in + count;
|
||||
*p++ = 0x80;
|
||||
|
||||
/* Bytes of padding needed to make 64 bytes */
|
||||
count = 64 - 1 - count;
|
||||
|
||||
/* Pad out to 56 mod 64 */
|
||||
if (count < 8) {
|
||||
/* Two lots of padding: Pad the first block to 64 bytes */
|
||||
memset(p, 0, count);
|
||||
byteReverse(ctx->u.in, 16);
|
||||
MD5Transform(ctx->buf, (uint32 *)ctx->u.in);
|
||||
|
||||
/* Now fill the next block with 56 bytes */
|
||||
memset(ctx->u.in, 0, 56);
|
||||
} else {
|
||||
/* Pad block to 56 bytes */
|
||||
memset(p, 0, count-8);
|
||||
}
|
||||
byteReverse(ctx->u.in, 14);
|
||||
|
||||
/* Append length in bits and transform */
|
||||
ctx->u.in32[14] = ctx->bits[0];
|
||||
ctx->u.in32[15] = ctx->bits[1];
|
||||
|
||||
MD5Transform(ctx->buf, (uint32 *)ctx->u.in);
|
||||
byteReverse((unsigned char *)ctx->buf, 4);
|
||||
memcpy(digest, ctx->buf, 16);
|
||||
memset(ctx, 0, sizeof(*ctx)); /* In case it is sensitive */
|
||||
}
|
||||
|
||||
/*
|
||||
** Convert a 128-bit MD5 digest into a 32-digit base-16 number.
|
||||
*/
|
||||
static void MD5DigestToBase16(unsigned char *digest, char *zBuf){
|
||||
static char const zEncode[] = "0123456789abcdef";
|
||||
int i, j;
|
||||
|
||||
for(j=i=0; i<16; i++){
|
||||
int a = digest[i];
|
||||
zBuf[j++] = zEncode[(a>>4)&0xf];
|
||||
zBuf[j++] = zEncode[a & 0xf];
|
||||
}
|
||||
zBuf[j] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** During testing, the special md5sum() aggregate function is available.
|
||||
** inside SQLite. The following routines implement that function.
|
||||
*/
|
||||
static void md5step(sqlite3_context *context, int argc, sqlite3_value **argv){
|
||||
MD5Context *p;
|
||||
int i;
|
||||
if( argc<1 ) return;
|
||||
p = sqlite3_aggregate_context(context, sizeof(*p));
|
||||
if( p==0 ) return;
|
||||
if( !p->isInit ){
|
||||
MD5Init(p);
|
||||
}
|
||||
for(i=0; i<argc; i++){
|
||||
const char *zData = (char*)sqlite3_value_text(argv[i]);
|
||||
if( zData ){
|
||||
MD5Update(p, (unsigned char*)zData, strlen(zData));
|
||||
}
|
||||
}
|
||||
}
|
||||
static void md5finalize(sqlite3_context *context){
|
||||
MD5Context *p;
|
||||
unsigned char digest[16];
|
||||
char zBuf[33];
|
||||
p = sqlite3_aggregate_context(context, sizeof(*p));
|
||||
MD5Final(digest,p);
|
||||
MD5DigestToBase16(digest, zBuf);
|
||||
sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
/*
|
||||
** End of copied md5sum() code.
|
||||
**************************************************************************/
|
||||
|
||||
typedef sqlite3_int64 i64;
|
||||
|
||||
typedef struct Error Error;
|
||||
typedef struct Sqlite Sqlite;
|
||||
typedef struct Statement Statement;
|
||||
|
||||
typedef struct Threadset Threadset;
|
||||
typedef struct Thread Thread;
|
||||
|
||||
/* Total number of errors in this process so far. */
|
||||
static int nGlobalErr = 0;
|
||||
|
||||
struct Error {
|
||||
int rc;
|
||||
int iLine;
|
||||
char *zErr;
|
||||
};
|
||||
|
||||
struct Sqlite {
|
||||
sqlite3 *db; /* Database handle */
|
||||
Statement *pCache; /* Linked list of cached statements */
|
||||
int nText; /* Size of array at aText[] */
|
||||
char **aText; /* Stored text results */
|
||||
};
|
||||
|
||||
struct Statement {
|
||||
sqlite3_stmt *pStmt; /* Pre-compiled statement handle */
|
||||
Statement *pNext; /* Next statement in linked-list */
|
||||
};
|
||||
|
||||
struct Thread {
|
||||
int iTid; /* Thread number within test */
|
||||
void* pArg; /* Pointer argument passed by caller */
|
||||
|
||||
pthread_t tid; /* Thread id */
|
||||
char *(*xProc)(int, void*); /* Thread main proc */
|
||||
Thread *pNext; /* Next in this list of threads */
|
||||
};
|
||||
|
||||
struct Threadset {
|
||||
int iMaxTid; /* Largest iTid value allocated so far */
|
||||
Thread *pThread; /* Linked list of threads */
|
||||
};
|
||||
|
||||
static void free_err(Error *p){
|
||||
sqlite3_free(p->zErr);
|
||||
p->zErr = 0;
|
||||
p->rc = 0;
|
||||
}
|
||||
|
||||
static void print_err(Error *p){
|
||||
if( p->rc!=SQLITE_OK ){
|
||||
int isWarn = 0;
|
||||
if( p->rc==SQLITE_SCHEMA ) isWarn = 1;
|
||||
if( sqlite3_strglob("* - no such table: *",p->zErr)==0 ) isWarn = 1;
|
||||
printf("%s: (%d) \"%s\" at line %d\n", isWarn ? "Warning" : "Error",
|
||||
p->rc, p->zErr, p->iLine);
|
||||
if( !isWarn ) nGlobalErr++;
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_and_free_err(Error *p){
|
||||
print_err(p);
|
||||
free_err(p);
|
||||
}
|
||||
|
||||
static void system_error(Error *pErr, int iSys){
|
||||
pErr->rc = iSys;
|
||||
pErr->zErr = (char *)sqlite3_malloc(512);
|
||||
strerror_r(iSys, pErr->zErr, 512);
|
||||
pErr->zErr[511] = '\0';
|
||||
}
|
||||
|
||||
static void sqlite_error(
|
||||
Error *pErr,
|
||||
Sqlite *pDb,
|
||||
const char *zFunc
|
||||
){
|
||||
pErr->rc = sqlite3_errcode(pDb->db);
|
||||
pErr->zErr = sqlite3_mprintf(
|
||||
"sqlite3_%s() - %s (%d)", zFunc, sqlite3_errmsg(pDb->db),
|
||||
sqlite3_extended_errcode(pDb->db)
|
||||
);
|
||||
}
|
||||
|
||||
static void test_error_x(
|
||||
Error *pErr,
|
||||
char *zErr
|
||||
){
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
pErr->rc = 1;
|
||||
pErr->zErr = zErr;
|
||||
}else{
|
||||
sqlite3_free(zErr);
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_error_x(
|
||||
Error *pErr,
|
||||
int rc
|
||||
){
|
||||
if( pErr->rc==rc ){
|
||||
pErr->rc = SQLITE_OK;
|
||||
sqlite3_free(pErr->zErr);
|
||||
pErr->zErr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int busyhandler(void *pArg, int n){
|
||||
usleep(10*1000);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void opendb_x(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
Sqlite *pDb, /* OUT: Database handle */
|
||||
const char *zFile, /* Database file name */
|
||||
int bDelete /* True to delete db file before opening */
|
||||
){
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
int rc;
|
||||
int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI;
|
||||
if( bDelete ) unlink(zFile);
|
||||
rc = sqlite3_open_v2(zFile, &pDb->db, flags, 0);
|
||||
if( rc ){
|
||||
sqlite_error(pErr, pDb, "open");
|
||||
sqlite3_close(pDb->db);
|
||||
pDb->db = 0;
|
||||
}else{
|
||||
sqlite3_create_function(
|
||||
pDb->db, "md5sum", -1, SQLITE_UTF8, 0, 0, md5step, md5finalize
|
||||
);
|
||||
sqlite3_busy_handler(pDb->db, busyhandler, 0);
|
||||
sqlite3_exec(pDb->db, "PRAGMA synchronous=OFF", 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void closedb_x(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
Sqlite *pDb /* OUT: Database handle */
|
||||
){
|
||||
int rc;
|
||||
int i;
|
||||
Statement *pIter;
|
||||
Statement *pNext;
|
||||
for(pIter=pDb->pCache; pIter; pIter=pNext){
|
||||
pNext = pIter->pNext;
|
||||
sqlite3_finalize(pIter->pStmt);
|
||||
sqlite3_free(pIter);
|
||||
}
|
||||
for(i=0; i<pDb->nText; i++){
|
||||
sqlite3_free(pDb->aText[i]);
|
||||
}
|
||||
sqlite3_free(pDb->aText);
|
||||
rc = sqlite3_close(pDb->db);
|
||||
if( rc && pErr->rc==SQLITE_OK ){
|
||||
pErr->zErr = sqlite3_mprintf("%s", sqlite3_errmsg(pDb->db));
|
||||
}
|
||||
memset(pDb, 0, sizeof(Sqlite));
|
||||
}
|
||||
|
||||
static void sql_script_x(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
Sqlite *pDb, /* Database handle */
|
||||
const char *zSql /* SQL script to execute */
|
||||
){
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
pErr->rc = sqlite3_exec(pDb->db, zSql, 0, 0, &pErr->zErr);
|
||||
}
|
||||
}
|
||||
|
||||
static void sql_script_printf_x(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
Sqlite *pDb, /* Database handle */
|
||||
const char *zFormat, /* SQL printf format string */
|
||||
... /* Printf args */
|
||||
){
|
||||
va_list ap; /* ... printf arguments */
|
||||
va_start(ap, zFormat);
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
char *zSql = sqlite3_vmprintf(zFormat, ap);
|
||||
pErr->rc = sqlite3_exec(pDb->db, zSql, 0, 0, &pErr->zErr);
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static Statement *getSqlStatement(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
Sqlite *pDb, /* Database handle */
|
||||
const char *zSql /* SQL statement */
|
||||
){
|
||||
Statement *pRet;
|
||||
int rc;
|
||||
|
||||
for(pRet=pDb->pCache; pRet; pRet=pRet->pNext){
|
||||
if( 0==strcmp(sqlite3_sql(pRet->pStmt), zSql) ){
|
||||
return pRet;
|
||||
}
|
||||
}
|
||||
|
||||
pRet = sqlite3_malloc(sizeof(Statement));
|
||||
rc = sqlite3_prepare_v2(pDb->db, zSql, -1, &pRet->pStmt, 0);
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite_error(pErr, pDb, "prepare_v2");
|
||||
return 0;
|
||||
}
|
||||
assert( 0==strcmp(sqlite3_sql(pRet->pStmt), zSql) );
|
||||
|
||||
pRet->pNext = pDb->pCache;
|
||||
pDb->pCache = pRet;
|
||||
return pRet;
|
||||
}
|
||||
|
||||
static sqlite3_stmt *getAndBindSqlStatement(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
Sqlite *pDb, /* Database handle */
|
||||
va_list ap /* SQL followed by parameters */
|
||||
){
|
||||
Statement *pStatement; /* The SQLite statement wrapper */
|
||||
sqlite3_stmt *pStmt; /* The SQLite statement to return */
|
||||
int i; /* Used to iterate through parameters */
|
||||
|
||||
pStatement = getSqlStatement(pErr, pDb, va_arg(ap, const char *));
|
||||
if( !pStatement ) return 0;
|
||||
pStmt = pStatement->pStmt;
|
||||
for(i=1; i<=sqlite3_bind_parameter_count(pStmt); i++){
|
||||
const char *zName = sqlite3_bind_parameter_name(pStmt, i);
|
||||
void * pArg = va_arg(ap, void*);
|
||||
|
||||
switch( zName[1] ){
|
||||
case 'i':
|
||||
sqlite3_bind_int64(pStmt, i, *(i64 *)pArg);
|
||||
break;
|
||||
|
||||
default:
|
||||
pErr->rc = 1;
|
||||
pErr->zErr = sqlite3_mprintf("Cannot discern type: \"%s\"", zName);
|
||||
pStmt = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pStmt;
|
||||
}
|
||||
|
||||
static i64 execsql_i64_x(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
Sqlite *pDb, /* Database handle */
|
||||
... /* SQL and pointers to parameter values */
|
||||
){
|
||||
i64 iRet = 0;
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
sqlite3_stmt *pStmt; /* SQL statement to execute */
|
||||
va_list ap; /* ... arguments */
|
||||
va_start(ap, pDb);
|
||||
pStmt = getAndBindSqlStatement(pErr, pDb, ap);
|
||||
if( pStmt ){
|
||||
int first = 1;
|
||||
while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
if( first && sqlite3_column_count(pStmt)>0 ){
|
||||
iRet = sqlite3_column_int64(pStmt, 0);
|
||||
}
|
||||
first = 0;
|
||||
}
|
||||
if( SQLITE_OK!=sqlite3_reset(pStmt) ){
|
||||
sqlite_error(pErr, pDb, "reset");
|
||||
}
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
return iRet;
|
||||
}
|
||||
|
||||
static char * execsql_text_x(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
Sqlite *pDb, /* Database handle */
|
||||
int iSlot, /* Db handle slot to store text in */
|
||||
... /* SQL and pointers to parameter values */
|
||||
){
|
||||
char *zRet = 0;
|
||||
|
||||
if( iSlot>=pDb->nText ){
|
||||
int nByte = sizeof(char *)*(iSlot+1);
|
||||
pDb->aText = (char **)sqlite3_realloc(pDb->aText, nByte);
|
||||
memset(&pDb->aText[pDb->nText], 0, sizeof(char*)*(iSlot+1-pDb->nText));
|
||||
pDb->nText = iSlot+1;
|
||||
}
|
||||
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
sqlite3_stmt *pStmt; /* SQL statement to execute */
|
||||
va_list ap; /* ... arguments */
|
||||
va_start(ap, iSlot);
|
||||
pStmt = getAndBindSqlStatement(pErr, pDb, ap);
|
||||
if( pStmt ){
|
||||
int first = 1;
|
||||
while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
if( first && sqlite3_column_count(pStmt)>0 ){
|
||||
zRet = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
|
||||
sqlite3_free(pDb->aText[iSlot]);
|
||||
pDb->aText[iSlot] = zRet;
|
||||
}
|
||||
first = 0;
|
||||
}
|
||||
if( SQLITE_OK!=sqlite3_reset(pStmt) ){
|
||||
sqlite_error(pErr, pDb, "reset");
|
||||
}
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
return zRet;
|
||||
}
|
||||
|
||||
static void integrity_check_x(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
Sqlite *pDb /* Database handle */
|
||||
){
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
Statement *pStatement; /* Statement to execute */
|
||||
char *zErr = 0; /* Integrity check error */
|
||||
|
||||
pStatement = getSqlStatement(pErr, pDb, "PRAGMA integrity_check");
|
||||
if( pStatement ){
|
||||
sqlite3_stmt *pStmt = pStatement->pStmt;
|
||||
while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
const char *z = (const char*)sqlite3_column_text(pStmt, 0);
|
||||
if( strcmp(z, "ok") ){
|
||||
if( zErr==0 ){
|
||||
zErr = sqlite3_mprintf("%s", z);
|
||||
}else{
|
||||
zErr = sqlite3_mprintf("%z\n%s", zErr, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_reset(pStmt);
|
||||
|
||||
if( zErr ){
|
||||
pErr->zErr = zErr;
|
||||
pErr->rc = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void *launch_thread_main(void *pArg){
|
||||
Thread *p = (Thread *)pArg;
|
||||
return (void *)p->xProc(p->iTid, p->pArg);
|
||||
}
|
||||
|
||||
static void launch_thread_x(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
Threadset *pThreads, /* Thread set */
|
||||
char *(*xProc)(int, void*), /* Proc to run */
|
||||
void *pArg /* Argument passed to thread proc */
|
||||
){
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
int iTid = ++pThreads->iMaxTid;
|
||||
Thread *p;
|
||||
int rc;
|
||||
|
||||
p = (Thread *)sqlite3_malloc(sizeof(Thread));
|
||||
memset(p, 0, sizeof(Thread));
|
||||
p->iTid = iTid;
|
||||
p->pArg = pArg;
|
||||
p->xProc = xProc;
|
||||
|
||||
rc = pthread_create(&p->tid, NULL, launch_thread_main, (void *)p);
|
||||
if( rc!=0 ){
|
||||
system_error(pErr, rc);
|
||||
sqlite3_free(p);
|
||||
}else{
|
||||
p->pNext = pThreads->pThread;
|
||||
pThreads->pThread = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void join_all_threads_x(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
Threadset *pThreads /* Thread set */
|
||||
){
|
||||
Thread *p;
|
||||
Thread *pNext;
|
||||
for(p=pThreads->pThread; p; p=pNext){
|
||||
void *ret;
|
||||
pNext = p->pNext;
|
||||
int rc;
|
||||
rc = pthread_join(p->tid, &ret);
|
||||
if( rc!=0 ){
|
||||
if( pErr->rc==SQLITE_OK ) system_error(pErr, rc);
|
||||
}else{
|
||||
printf("Thread %d says: %s\n", p->iTid, (ret==0 ? "..." : (char *)ret));
|
||||
fflush(stdout);
|
||||
}
|
||||
sqlite3_free(p);
|
||||
}
|
||||
pThreads->pThread = 0;
|
||||
}
|
||||
|
||||
static i64 filesize_x(
|
||||
Error *pErr,
|
||||
const char *zFile
|
||||
){
|
||||
i64 iRet = 0;
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
struct stat sStat;
|
||||
if( stat(zFile, &sStat) ){
|
||||
iRet = -1;
|
||||
}else{
|
||||
iRet = sStat.st_size;
|
||||
}
|
||||
}
|
||||
return iRet;
|
||||
}
|
||||
|
||||
static void filecopy_x(
|
||||
Error *pErr,
|
||||
const char *zFrom,
|
||||
const char *zTo
|
||||
){
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
i64 nByte = filesize_x(pErr, zFrom);
|
||||
if( nByte<0 ){
|
||||
test_error_x(pErr, sqlite3_mprintf("no such file: %s", zFrom));
|
||||
}else{
|
||||
i64 iOff;
|
||||
char aBuf[1024];
|
||||
int fd1;
|
||||
int fd2;
|
||||
unlink(zTo);
|
||||
|
||||
fd1 = open(zFrom, O_RDONLY);
|
||||
if( fd1<0 ){
|
||||
system_error(pErr, errno);
|
||||
return;
|
||||
}
|
||||
fd2 = open(zTo, O_RDWR|O_CREAT|O_EXCL, 0644);
|
||||
if( fd2<0 ){
|
||||
system_error(pErr, errno);
|
||||
close(fd1);
|
||||
return;
|
||||
}
|
||||
|
||||
iOff = 0;
|
||||
while( iOff<nByte ){
|
||||
int nCopy = sizeof(aBuf);
|
||||
if( nCopy+iOff>nByte ){
|
||||
nCopy = nByte - iOff;
|
||||
}
|
||||
if( nCopy!=read(fd1, aBuf, nCopy) ){
|
||||
system_error(pErr, errno);
|
||||
break;
|
||||
}
|
||||
if( nCopy!=write(fd2, aBuf, nCopy) ){
|
||||
system_error(pErr, errno);
|
||||
break;
|
||||
}
|
||||
iOff += nCopy;
|
||||
}
|
||||
|
||||
close(fd1);
|
||||
close(fd2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Used by setstoptime() and timetostop().
|
||||
*/
|
||||
static double timelimit = 0.0;
|
||||
|
||||
static double currentTime(void){
|
||||
double t;
|
||||
static sqlite3_vfs *pTimelimitVfs = 0;
|
||||
if( pTimelimitVfs==0 ) pTimelimitVfs = sqlite3_vfs_find(0);
|
||||
if( pTimelimitVfs->iVersion>=2 && pTimelimitVfs->xCurrentTimeInt64!=0 ){
|
||||
sqlite3_int64 tm;
|
||||
pTimelimitVfs->xCurrentTimeInt64(pTimelimitVfs, &tm);
|
||||
t = tm/86400000.0;
|
||||
}else{
|
||||
pTimelimitVfs->xCurrentTime(pTimelimitVfs, &t);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
static void setstoptime_x(
|
||||
Error *pErr, /* IN/OUT: Error code */
|
||||
int nMs /* Milliseconds until "stop time" */
|
||||
){
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
double t = currentTime();
|
||||
timelimit = t + ((double)nMs)/(1000.0*60.0*60.0*24.0);
|
||||
}
|
||||
}
|
||||
|
||||
static int timetostop_x(
|
||||
Error *pErr /* IN/OUT: Error code */
|
||||
){
|
||||
int ret = 1;
|
||||
if( pErr->rc==SQLITE_OK ){
|
||||
double t = currentTime();
|
||||
ret = (t >= timelimit);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*************************************************************************
|
||||
**************************************************************************
|
||||
|
||||
+1035
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -280,11 +280,11 @@ ifcapable wal {
|
||||
INSERT INTO t2 VALUES('x', 'y');
|
||||
}
|
||||
lsort [array names ::T1]
|
||||
} {test.db1 test.db1-journal test.db1-wal}
|
||||
} {test.db1 test.db1-journal test.db1-wal test.db1-wal2}
|
||||
|
||||
do_test 5.1.2 {
|
||||
lsort [array names ::T2]
|
||||
} {test.db2 test.db2-journal test.db2-wal}
|
||||
} {test.db2 test.db2-journal test.db2-wal test.db2-wal2}
|
||||
db close
|
||||
|
||||
tvfs1 delete
|
||||
|
||||
+6
-6
@@ -1174,7 +1174,7 @@ foreach {tn pgsz works} {
|
||||
9 32768 1
|
||||
10 65536 1
|
||||
11 131072 0
|
||||
11 1016 0
|
||||
12 1016 0
|
||||
} {
|
||||
|
||||
if {$::SQLITE_MAX_PAGE_SIZE < $pgsz} {
|
||||
@@ -1184,14 +1184,14 @@ foreach {tn pgsz works} {
|
||||
for {set pg 1} {$pg <= 3} {incr pg} {
|
||||
forcecopy testX.db test.db
|
||||
forcedelete test.db-wal
|
||||
|
||||
|
||||
# Check that the database now exists and consists of three pages. And
|
||||
# that there is no associated wal file.
|
||||
#
|
||||
do_test wal-18.2.$tn.$pg.1 { file exists test.db-wal } 0
|
||||
do_test wal-18.2.$tn.$pg.2 { file exists test.db } 1
|
||||
do_test wal-18.2.$tn.$pg.3 { file size test.db } [expr 1024*3]
|
||||
|
||||
|
||||
do_test wal-18.2.$tn.$pg.4 {
|
||||
|
||||
# Create a wal file that contains a single frame (database page
|
||||
@@ -1223,16 +1223,16 @@ foreach {tn pgsz works} {
|
||||
puts -nonewline $fd $framehdr
|
||||
puts -nonewline $fd $framebody
|
||||
close $fd
|
||||
|
||||
|
||||
file size test.db-wal
|
||||
} [wal_file_size 1 $pgsz]
|
||||
|
||||
|
||||
do_test wal-18.2.$tn.$pg.5 {
|
||||
sqlite3 db test.db
|
||||
set rc [catch { db one {PRAGMA integrity_check} } msg]
|
||||
expr { $rc!=0 || $msg!="ok" }
|
||||
} $works
|
||||
|
||||
|
||||
db close
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,43 +35,6 @@ proc cond_incr_sync_count {adj} {
|
||||
}
|
||||
}
|
||||
|
||||
proc set_tvfs_hdr {file args} {
|
||||
|
||||
# Set $nHdr to the number of bytes in the wal-index header:
|
||||
set nHdr 48
|
||||
set nInt [expr {$nHdr/4}]
|
||||
|
||||
if {[llength $args]>2} {
|
||||
error {wrong # args: should be "set_tvfs_hdr fileName ?val1? ?val2?"}
|
||||
}
|
||||
|
||||
set blob [tvfs shm $file]
|
||||
if {$::tcl_platform(byteOrder)=="bigEndian"} {set fmt I} {set fmt i}
|
||||
|
||||
if {[llength $args]} {
|
||||
set ia [lindex $args 0]
|
||||
set ib $ia
|
||||
if {[llength $args]==2} {
|
||||
set ib [lindex $args 1]
|
||||
}
|
||||
binary scan $blob a[expr $nHdr*2]a* dummy tail
|
||||
set blob [binary format ${fmt}${nInt}${fmt}${nInt}a* $ia $ib $tail]
|
||||
tvfs shm $file $blob
|
||||
}
|
||||
|
||||
binary scan $blob ${fmt}${nInt} ints
|
||||
return $ints
|
||||
}
|
||||
|
||||
proc incr_tvfs_hdr {file idx incrval} {
|
||||
set ints [set_tvfs_hdr $file]
|
||||
set v [lindex $ints $idx]
|
||||
incr v $incrval
|
||||
lset ints $idx $v
|
||||
set_tvfs_hdr $file $ints
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test case wal2-1.*:
|
||||
#
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# 2017 September 19
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the operation of the library in
|
||||
# "PRAGMA journal_mode=WAL2" mode.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
|
||||
set testprefix wal2big
|
||||
ifcapable !wal {finish_test ; return }
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
CREATE INDEX t1a ON t1(a);
|
||||
CREATE INDEX t1b ON t1(b);
|
||||
CREATE INDEX t1c ON t1(c);
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_size_limit = 10000000;
|
||||
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<200000
|
||||
)
|
||||
INSERT INTO t1 SELECT random(), random(), random() FROM s;
|
||||
} {wal2 10000000}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<200000
|
||||
)
|
||||
INSERT INTO t1 SELECT random(), random(), random() FROM s;
|
||||
}
|
||||
|
||||
do_test 1.2 {
|
||||
list [expr [file size test.db-wal]>10000000] \
|
||||
[expr [file size test.db-wal2]>10000000]
|
||||
} {1 1}
|
||||
|
||||
do_test 1.3 {
|
||||
sqlite3 db2 test.db
|
||||
execsql {
|
||||
SELECT count(*) FROM t1;
|
||||
PRAGMA integrity_check;
|
||||
} db2
|
||||
} {400000 ok}
|
||||
|
||||
do_test 1.4 {
|
||||
db2 close
|
||||
forcecopy test.db test.db2
|
||||
forcecopy test.db-wal test.db2-wal
|
||||
forcecopy test.db-wal2 test.db2-wal2
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT count(*) FROM t1;
|
||||
PRAGMA integrity_check;
|
||||
}
|
||||
} {400000 ok}
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,164 @@
|
||||
# 2018 December 6
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
set ::testprefix wal2concurrent
|
||||
|
||||
ifcapable !concurrent {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Warm-body test.
|
||||
#
|
||||
foreach tn {1 2} {
|
||||
reset_db
|
||||
sqlite3 db2 test.db
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA page_size = 1024;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(y);
|
||||
PRAGMA journal_size_limit = 5000;
|
||||
PRAGMA journal_mode = wal2;
|
||||
} {5000 wal2}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
INSERT INTO t1 VALUES(1);
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(2);
|
||||
} {}
|
||||
|
||||
do_test 1.2 {
|
||||
execsql {
|
||||
PRAGMA journal_size_limit = 5000;
|
||||
INSERT INTO t1 VALUES(3)
|
||||
} db2
|
||||
catchsql { COMMIT }
|
||||
} {1 {database is locked}}
|
||||
|
||||
do_catchsql_test 1.3 { COMMIT } {1 {database is locked}}
|
||||
do_catchsql_test 1.4 { ROLLBACK } {0 {}}
|
||||
|
||||
do_test 1.5 {
|
||||
list [file size test.db-wal] [file size test.db-wal2]
|
||||
} {2128 0}
|
||||
|
||||
do_execsql_test 1.6 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(2);
|
||||
} {}
|
||||
|
||||
do_test 1.7 {
|
||||
execsql { INSERT INTO t2 VALUES(randomblob(4000)) } db2
|
||||
list [file size test.db-wal] [file size test.db-wal2]
|
||||
} {7368 0}
|
||||
|
||||
if {$tn==1} {
|
||||
do_test 1.8 {
|
||||
execsql {
|
||||
INSERT INTO t2 VALUES(1);
|
||||
INSERT INTO t1 VALUES(5);
|
||||
} db2
|
||||
list [file size test.db-wal] [file size test.db-wal2]
|
||||
} {7368 2128}
|
||||
|
||||
do_catchsql_test 1.9 { COMMIT } {1 {database is locked}}
|
||||
do_catchsql_test 1.10 { ROLLBACK } {0 {}}
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
do_execsql_test 1.11 { SELECT * FROM t1 } {1 3 5}
|
||||
do_execsql_test 1.12 { SELECT count(*) FROM t2 } {2}
|
||||
} else {
|
||||
do_test 1.8 {
|
||||
execsql {
|
||||
INSERT INTO t2 VALUES(1);
|
||||
} db2
|
||||
list [file size test.db-wal] [file size test.db-wal2]
|
||||
} {7368 1080}
|
||||
|
||||
do_catchsql_test 1.9 { COMMIT } {0 {}}
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
do_execsql_test 1.11 { SELECT * FROM t1 } {1 3 2}
|
||||
do_execsql_test 1.12 { SELECT count(*) FROM t2 } {2}
|
||||
|
||||
do_test 1.13 {
|
||||
list [file size test.db-wal] [file size test.db-wal2]
|
||||
} {7368 2128}
|
||||
}
|
||||
}
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test 2.$tn.1 {
|
||||
sql1 {
|
||||
PRAGMA auto_vacuum = OFF;
|
||||
CREATE TABLE t1(x UNIQUE);
|
||||
CREATE TABLE t2(x UNIQUE);
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_size_limit = 15000;
|
||||
}
|
||||
} {wal2 15000}
|
||||
|
||||
do_test 2.$tn.2 {
|
||||
sql1 {
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<=10
|
||||
)
|
||||
INSERT INTO t1 SELECT randomblob(800) FROM s;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.3 {
|
||||
sql1 { DELETE FROM t1 WHERE (rowid%4)==0 }
|
||||
list [expr [file size test.db-wal]>15000] \
|
||||
[expr [file size test.db-wal2]>15000]
|
||||
} {1 0}
|
||||
|
||||
do_test 2.$tn.4 {
|
||||
sql1 { PRAGMA wal_checkpoint; }
|
||||
sql1 {
|
||||
BEGIN CONCURRENT;
|
||||
INSERT INTO t1 VALUES(randomblob(800));
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 2.$tn.5 {
|
||||
sql2 {
|
||||
PRAGMA journal_size_limit = 15000;
|
||||
INSERT INTO t2 VALUES(randomblob(800));
|
||||
INSERT INTO t2 VALUES(randomblob(800));
|
||||
INSERT INTO t2 VALUES(randomblob(800));
|
||||
INSERT INTO t2 VALUES(randomblob(800));
|
||||
INSERT INTO t2 VALUES(randomblob(800));
|
||||
DELETE FROM t2;
|
||||
}
|
||||
list [expr [file size test.db-wal]>15000] \
|
||||
[expr [file size test.db-wal2]>15000]
|
||||
} {1 1}
|
||||
|
||||
do_test 2.$tn.6 {
|
||||
sql1 {
|
||||
INSERT INTO t1 VALUES(randomblob(800));
|
||||
COMMIT;
|
||||
PRAGMA integrity_check;
|
||||
}
|
||||
} {ok}
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# 2010 May 03
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the operation of the library in
|
||||
# "PRAGMA journal_mode=WAL" mode.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
|
||||
ifcapable !wal {finish_test ; return }
|
||||
set testprefix wal2fault
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(x,y);
|
||||
PRAGMA journal_mode = wal2;
|
||||
WITH s(i) AS ( SELECT 100 UNION ALL SELECT i-1 FROM s WHERE (i-1)>0 )
|
||||
INSERT INTO t1 SELECT i, randomblob(i) FROM s;
|
||||
WITH s(i) AS ( SELECT 100 UNION ALL SELECT i-1 FROM s WHERE (i-1)>0 )
|
||||
INSERT INTO t1 SELECT i, randomblob(i) FROM s;
|
||||
} {wal2}
|
||||
|
||||
do_test 1.1 {
|
||||
expr [file size test.db-wal]>10000
|
||||
} {1}
|
||||
faultsim_save_and_close
|
||||
|
||||
do_faultsim_test 1 -prep {
|
||||
faultsim_restore_and_reopen
|
||||
execsql {
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
SELECT count(*) FROM sqlite_master;
|
||||
}
|
||||
} -body {
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
}
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,106 @@
|
||||
# 2018 December 15
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the operation of the library in
|
||||
# "PRAGMA journal_mode=WAL2" mode.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
|
||||
set testprefix wal2lock
|
||||
ifcapable !wal {finish_test ; return }
|
||||
|
||||
db close
|
||||
testvfs tvfs
|
||||
sqlite3 db test.db -vfs tvfs
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA journal_mode = wal2;
|
||||
CREATE TABLE y1(y, yy);
|
||||
CREATE INDEX y1y ON y1(y);
|
||||
CREATE INDEX y1yy ON y1(yy);
|
||||
INSERT INTO y1 VALUES(1, 2), (3, 4), (5, 6);
|
||||
} {wal2}
|
||||
|
||||
tvfs script vfs_callback
|
||||
tvfs filter xShmLock
|
||||
|
||||
set ::lock [list]
|
||||
proc vfs_callback {func file name lock} {
|
||||
lappend ::lock $lock
|
||||
return SQLITE_OK
|
||||
}
|
||||
|
||||
do_execsql_test 1.1.1 {
|
||||
SELECT * FROM y1
|
||||
} {1 2 3 4 5 6}
|
||||
do_test 1.1.2 {
|
||||
set ::lock
|
||||
} {{4 1 lock shared} {4 1 unlock shared}}
|
||||
|
||||
set ::bFirst 1
|
||||
proc vfs_callback {func file name lock} {
|
||||
if {$::bFirst} {
|
||||
set ::bFirst 0
|
||||
return SQLITE_BUSY
|
||||
}
|
||||
return SQLITE_OK
|
||||
}
|
||||
do_execsql_test 1.2 {
|
||||
SELECT * FROM y1
|
||||
} {1 2 3 4 5 6}
|
||||
|
||||
set ::bFirst 1
|
||||
proc vfs_callback {func file name lock} {
|
||||
if {$::bFirst} {
|
||||
set ::bFirst 0
|
||||
return SQLITE_IOERR
|
||||
}
|
||||
return SQLITE_OK
|
||||
}
|
||||
do_catchsql_test 1.3 {
|
||||
SELECT * FROM y1
|
||||
} {1 {disk I/O error}}
|
||||
|
||||
puts "# Warning: This next test case causes SQLite to call xSleep(1) 100 times."
|
||||
puts "# Normally this equates to a delay of roughly 10 seconds, but if SQLite"
|
||||
puts "# is built on unix without HAVE_USLEEP defined, it may be much longer."
|
||||
proc vfs_callback {func file name lock} { return SQLITE_BUSY }
|
||||
do_catchsql_test 1.4 {
|
||||
SELECT * FROM y1
|
||||
} {1 {locking protocol}}
|
||||
proc vfs_callback {func file name lock} { return SQLITE_OK }
|
||||
|
||||
sqlite3 db2 test.db -vfs tvfs
|
||||
set ::bFirst 1
|
||||
|
||||
proc vfs_callback {func file name lock} {
|
||||
if {$::bFirst} {
|
||||
set ::bFirst 0
|
||||
db2 eval { INSERT INTO y1 VALUES(7, 8) }
|
||||
}
|
||||
}
|
||||
|
||||
do_execsql_test 1.5.1 {
|
||||
SELECT * FROM y1
|
||||
} {1 2 3 4 5 6 7 8}
|
||||
do_execsql_test 1.5.2 {
|
||||
SELECT * FROM y1
|
||||
} {1 2 3 4 5 6 7 8}
|
||||
|
||||
db close
|
||||
db2 close
|
||||
tvfs delete
|
||||
finish_test
|
||||
@@ -0,0 +1,265 @@
|
||||
# 2018 December 13
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the operation of the library in
|
||||
# "PRAGMA journal_mode=WAL2" mode.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
|
||||
set testprefix wal2recover
|
||||
ifcapable !wal {finish_test ; return }
|
||||
|
||||
proc db_copy {from to} {
|
||||
forcecopy $from $to
|
||||
forcecopy ${from}-wal ${to}-wal
|
||||
forcecopy ${from}-wal2 ${to}-wal2
|
||||
}
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
CREATE INDEX t1a ON t1(a);
|
||||
CREATE INDEX t1b ON t1(b);
|
||||
CREATE INDEX t1c ON t1(c);
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_size_limit = 15000;
|
||||
PRAGMA wal_autocheckpoint = 0;
|
||||
} {wal2 15000 0}
|
||||
|
||||
do_test 1.1 {
|
||||
for {set i 1} {$i <= 1000} {incr i} {
|
||||
execsql { INSERT INTO t1 VALUES(random(), random(), random()) }
|
||||
db_copy test.db test.db2
|
||||
sqlite3 db2 test.db
|
||||
set res [execsql {
|
||||
SELECT count(*) FROM t1;
|
||||
PRAGMA integrity_check;
|
||||
} db2]
|
||||
db2 close
|
||||
if {$res != [list $i ok]} {
|
||||
error "failure on iteration $i"
|
||||
}
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 2.0 {
|
||||
CREATE TABLE t1(x UNIQUE);
|
||||
CREATE TABLE t2(x UNIQUE);
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
PRAGMA wal_autocheckpoint = 0;
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(randomblob(4000));
|
||||
INSERT INTO t1 VALUES(randomblob(4000));
|
||||
INSERT INTO t1 VALUES(randomblob(4000));
|
||||
COMMIT;
|
||||
BEGIN;
|
||||
INSERT INTO t2 VALUES(randomblob(4000));
|
||||
INSERT INTO t2 VALUES(randomblob(4000));
|
||||
INSERT INTO t2 VALUES(randomblob(4000));
|
||||
COMMIT;
|
||||
} {wal2 10000 0}
|
||||
do_test 2.0.1 {
|
||||
list [file size test.db] [file size test.db-wal] [file size test.db-wal2]
|
||||
} {5120 28328 28328}
|
||||
|
||||
# Test recovery with both wal files intact.
|
||||
#
|
||||
do_test 2.1 {
|
||||
db_copy test.db test.db2
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT count(*) FROM t1;
|
||||
SELECT count(*) FROM t2;
|
||||
PRAGMA integrity_check;
|
||||
} db2
|
||||
} {3 3 ok}
|
||||
|
||||
do_test 2.2 {
|
||||
db2 close
|
||||
db_copy test.db test.db2
|
||||
hexio_write test.db2-wal 16 12345678
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT count(*) FROM t1;
|
||||
SELECT count(*) FROM t2;
|
||||
} db2
|
||||
} {0 3}
|
||||
|
||||
do_test 2.3 {
|
||||
db2 close
|
||||
db_copy test.db test.db2
|
||||
hexio_write test.db2-wal2 16 12345678
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT count(*) FROM t1;
|
||||
SELECT count(*) FROM t2;
|
||||
PRAGMA integrity_check;
|
||||
} db2
|
||||
} {3 0 ok}
|
||||
|
||||
do_test 2.4 {
|
||||
db2 close
|
||||
db_copy test.db test.db2
|
||||
forcecopy test.db-wal test.db2-wal2
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT count(*) FROM t1;
|
||||
SELECT count(*) FROM t2;
|
||||
PRAGMA integrity_check;
|
||||
} db2
|
||||
} {3 0 ok}
|
||||
|
||||
do_test 2.5 {
|
||||
db2 close
|
||||
db_copy test.db test.db2
|
||||
forcecopy test.db-wal test.db2-wal2
|
||||
forcecopy test.db-wal2 test.db2-wal
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT count(*) FROM t1;
|
||||
SELECT count(*) FROM t2;
|
||||
PRAGMA integrity_check;
|
||||
} db2
|
||||
} {3 3 ok}
|
||||
|
||||
do_test 2.6 {
|
||||
db2 close
|
||||
db_copy test.db test.db2
|
||||
forcecopy test.db-wal test.db2-wal2
|
||||
close [open test.db-wal w]
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT count(*) FROM t1;
|
||||
SELECT count(*) FROM t2;
|
||||
PRAGMA integrity_check;
|
||||
} db2
|
||||
} {3 0 ok}
|
||||
|
||||
do_test 2.7 {
|
||||
db2 close
|
||||
db_copy test.db test.db2
|
||||
forcedelete test.db2-wal
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT count(*) FROM t1;
|
||||
SELECT count(*) FROM t2;
|
||||
PRAGMA integrity_check;
|
||||
} db2
|
||||
} {0 0 ok}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE t1(a TEXT, b TEXT, c TEXT);
|
||||
CREATE INDEX t1a ON t1(a);
|
||||
CREATE INDEX t1b ON t1(b);
|
||||
CREATE INDEX t1c ON t1(c);
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
PRAGMA wal_autocheckpoint = 0;
|
||||
PRAGMA cache_size = 5;
|
||||
} {wal2 10000 0}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s where i < 200)
|
||||
INSERT INTO t1 SELECT i, i, i FROM s;
|
||||
|
||||
INSERT INTO t1 VALUES(201, 201, 201);
|
||||
} {}
|
||||
|
||||
do_test 3.2 {
|
||||
list [file size test.db] [file size test.db-wal] [file size test.db-wal2]
|
||||
} {5120 15752 4224}
|
||||
|
||||
do_test 3.3 {
|
||||
forcecopy test.db test.db2
|
||||
forcecopy test.db-wal test.db2-wal
|
||||
forcecopy test.db-wal2 test.db2-wal2
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
PRAGMA wal_autocheckpoint = 0;
|
||||
PRAGMA cache_size = 5;
|
||||
BEGIN;
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s where i < 200)
|
||||
INSERT INTO t1 SELECT i, i, i FROM s;
|
||||
} db2
|
||||
list [file size test.db2] [file size test.db2-wal] [file size test.db2-wal2]
|
||||
} {5120 15752 23088}
|
||||
|
||||
do_test 3.4 {
|
||||
set fd [open test.db2-shm]
|
||||
fconfigure $fd -encoding binary -translation binary
|
||||
set data [read $fd]
|
||||
close $fd
|
||||
|
||||
set fd [open test.db-shm w]
|
||||
fconfigure $fd -encoding binary -translation binary
|
||||
puts -nonewline $fd $data
|
||||
close $fd
|
||||
|
||||
execsql {
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s where i < 10)
|
||||
INSERT INTO t1 SELECT i, i, i FROM s;
|
||||
SELECT count(*) FROM t1;
|
||||
PRAGMA integrity_check;
|
||||
}
|
||||
} {211 ok}
|
||||
|
||||
do_test 3.5 {
|
||||
list [file size test.db] [file size test.db-wal] [file size test.db-wal2]
|
||||
} {5120 15752 18896}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 4.0 {
|
||||
PRAGMA journal_mode = wal2;
|
||||
CREATE TABLE xyz(x, y, z);
|
||||
INSERT INTO xyz VALUES('x', 'y', 'z');
|
||||
} {wal2}
|
||||
db close
|
||||
do_test 4.1 {
|
||||
close [open test.db-wal w]
|
||||
file mkdir test.db-wal2
|
||||
sqlite3 db test.db
|
||||
catchsql { SELECT * FROM xyz }
|
||||
} {1 {unable to open database file}}
|
||||
db close
|
||||
file delete test.db-wal2
|
||||
|
||||
do_test 4.2 {
|
||||
sqlite3 db test.db
|
||||
execsql {
|
||||
INSERT INTO xyz VALUES('a', 'b', 'c');
|
||||
}
|
||||
forcecopy test.db test.db2
|
||||
forcecopy test.db-wal test.db2-wal
|
||||
forcedelete test.db2-wal2
|
||||
file mkdir test.db2-wal2
|
||||
sqlite3 db2 test.db2
|
||||
catchsql { SELECT * FROM xyz } db2
|
||||
} {1 {unable to open database file}}
|
||||
db2 close
|
||||
file delete test.db2-wal2
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
# 2018 December 13
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the operation of the library in
|
||||
# "PRAGMA journal_mode=WAL2" mode.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
|
||||
set testprefix wal2recover2
|
||||
ifcapable !wal {finish_test ; return }
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(x);
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1500 )
|
||||
INSERT INTO t1 SELECT i FROM s;
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1500 )
|
||||
INSERT INTO t2 SELECT i FROM s;
|
||||
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
} {wal2 10000}
|
||||
|
||||
set ::L 1125750
|
||||
set ::M 1126500
|
||||
set ::H 1127250
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
UPDATE t1 SET x=x+1;
|
||||
UPDATE t2 SET x=x+1 WHERE rowid<=750;
|
||||
|
||||
SELECT sum(x) FROM t1;
|
||||
SELECT sum(x) FROM t2;
|
||||
} [list $H $M]
|
||||
|
||||
do_test 1.2 {
|
||||
list [file size test.db] [file size test.db-wal] [file size test.db-wal2]
|
||||
} {31744 14704 7368}
|
||||
|
||||
proc cksum {zIn data} {
|
||||
if {[string length $zIn]==0} {
|
||||
set s0 0
|
||||
set s1 0
|
||||
} else {
|
||||
set s0 [hexio_get_int [string range $zIn 0 7]]
|
||||
set s1 [hexio_get_int [string range $zIn 8 15]]
|
||||
}
|
||||
set n [expr [string length $data] / 8]
|
||||
|
||||
for {set i 0} {$i < $n} {incr i 2} {
|
||||
set x0 [hexio_get_int -l [string range $data [expr $i*8] [expr $i*8+7]]]
|
||||
set x1 [hexio_get_int -l [string range $data [expr $i*8+8] [expr $i*8+8+7]]]
|
||||
|
||||
set s0 [expr ($s0 + $x0 + $s1) & 0xFFFFFFFF]
|
||||
set s1 [expr ($s1 + $x1 + $s0) & 0xFFFFFFFF]
|
||||
}
|
||||
|
||||
return "[hexio_render_int32 $s0][hexio_render_int32 $s1]"
|
||||
}
|
||||
|
||||
proc fix_wal_cksums {file} {
|
||||
# Fix the checksum on the wal header.
|
||||
set data [hexio_read $file 0 32]
|
||||
set cksum [cksum {} [string range $data 0 47]]
|
||||
set salt [hexio_read $file 16 8]
|
||||
hexio_write $file 24 $cksum
|
||||
|
||||
# Fix the checksums for all pages in the wal file.
|
||||
set pgsz [hexio_get_int [hexio_read $file 8 4]]
|
||||
set sz [file size $file]
|
||||
for {set off 32} {$off < $sz} {incr off [expr $pgsz+24]} {
|
||||
set e [hexio_read $file $off 8]
|
||||
set cksum [cksum $cksum $e]
|
||||
|
||||
set p [hexio_read $file [expr $off+24] $pgsz]
|
||||
set cksum [cksum $cksum $p]
|
||||
|
||||
hexio_write $file [expr $off+8] $salt
|
||||
hexio_write $file [expr $off+16] $cksum
|
||||
}
|
||||
}
|
||||
|
||||
proc wal_incr_hdrfield {file field} {
|
||||
switch -- $field {
|
||||
nCkpt { set offset 12 }
|
||||
salt0 { set offset 16 }
|
||||
salt1 { set offset 20 }
|
||||
default {
|
||||
error "unknown field $field - should be \"nCkpt\", \"salt0\" or \"salt1\""
|
||||
}
|
||||
}
|
||||
|
||||
# Increment the value in the wal header.
|
||||
set v [hexio_get_int [hexio_read $file $offset 4]]
|
||||
incr v
|
||||
hexio_write $file $offset [hexio_render_int32 $v]
|
||||
|
||||
# Fix various checksums
|
||||
fix_wal_cksums $file
|
||||
}
|
||||
|
||||
proc wal_set_nckpt {file val} {
|
||||
# Increment the value in the wal header.
|
||||
hexio_write $file 12 [hexio_render_int32 $val]
|
||||
|
||||
# Fix various checksums
|
||||
fix_wal_cksums $file
|
||||
}
|
||||
|
||||
proc wal_set_follow {file prevfile} {
|
||||
set pgsz [hexio_get_int [hexio_read $prevfile 8 4]]
|
||||
set sz [file size $prevfile]
|
||||
set cksum [hexio_read $prevfile [expr $sz-$pgsz-8] 8]
|
||||
|
||||
hexio_write $file 16 $cksum
|
||||
fix_wal_cksums $file
|
||||
}
|
||||
|
||||
foreach {tn file field} {
|
||||
1 test.db2-wal salt0
|
||||
2 test.db2-wal salt1
|
||||
3 test.db2-wal nCkpt
|
||||
4 test.db2-wal2 salt0
|
||||
5 test.db2-wal2 salt1
|
||||
6 test.db2-wal2 nCkpt
|
||||
} {
|
||||
do_test 1.3.$tn {
|
||||
forcecopy test.db test.db2
|
||||
forcecopy test.db-wal test.db2-wal
|
||||
forcecopy test.db-wal2 test.db2-wal2
|
||||
wal_incr_hdrfield $file $field
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT sum(x) FROM t1;
|
||||
SELECT sum(x) FROM t2;
|
||||
} db2
|
||||
} [list $H $L]
|
||||
db2 close
|
||||
}
|
||||
|
||||
do_test 1.4 {
|
||||
forcecopy test.db test.db2
|
||||
forcecopy test.db-wal2 test.db2-wal
|
||||
forcedelete test.db2-wal2
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT sum(x) FROM t1;
|
||||
SELECT sum(x) FROM t2;
|
||||
} db2
|
||||
} [list $L $M]
|
||||
|
||||
do_test 1.5 {
|
||||
forcecopy test.db test.db2
|
||||
forcecopy test.db-wal2 test.db2-wal
|
||||
forcecopy test.db-wal test.db2-wal2
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT sum(x) FROM t1;
|
||||
SELECT sum(x) FROM t2;
|
||||
} db2
|
||||
} [list $H $M]
|
||||
|
||||
foreach {tn file field} {
|
||||
1 test.db2-wal salt0
|
||||
2 test.db2-wal salt1
|
||||
3 test.db2-wal2 salt0
|
||||
4 test.db2-wal2 salt1
|
||||
} {
|
||||
do_test 1.6.$tn {
|
||||
forcecopy test.db test.db2
|
||||
forcecopy test.db-wal2 test.db2-wal
|
||||
forcecopy test.db-wal test.db2-wal2
|
||||
wal_incr_hdrfield $file $field
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT sum(x) FROM t1;
|
||||
SELECT sum(x) FROM t2;
|
||||
} db2
|
||||
} [list $H $L]
|
||||
db2 close
|
||||
}
|
||||
|
||||
foreach {tn nCkpt1 nCkpt2 res} [list \
|
||||
1 2 1 "$H $M" \
|
||||
2 2 2 "$L $M" \
|
||||
3 3 1 "$H $L" \
|
||||
4 15 14 "$H $M" \
|
||||
5 0 15 "$H $M" \
|
||||
6 1 15 "$L $M" \
|
||||
] {
|
||||
do_test 1.7.$tn {
|
||||
forcecopy test.db test.db2
|
||||
forcecopy test.db-wal2 test.db2-wal
|
||||
forcecopy test.db-wal test.db2-wal2
|
||||
|
||||
wal_set_nckpt test.db2-wal2 $nCkpt2
|
||||
wal_set_nckpt test.db2-wal $nCkpt1
|
||||
wal_set_follow test.db2-wal test.db2-wal2
|
||||
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
execsql {
|
||||
SELECT sum(x) FROM t1;
|
||||
SELECT sum(x) FROM t2;
|
||||
} db2
|
||||
} $res
|
||||
db2 close
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 1.8.1 {
|
||||
PRAGMA autovacuum = 0;
|
||||
PRAGMA page_size = 4096;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE t2(x);
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1500 )
|
||||
INSERT INTO t1 SELECT i FROM s;
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1500 )
|
||||
INSERT INTO t2 SELECT i FROM s;
|
||||
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
|
||||
WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1500 )
|
||||
INSERT INTO t2 SELECT i FROM s;
|
||||
} {wal2 10000}
|
||||
|
||||
do_test 1.8.2 {
|
||||
list [file size test.db-wal] [file size test.db-wal2]
|
||||
} {24752 0}
|
||||
|
||||
do_execsql_test 1.8.3 { PRAGMA user_version = 123 }
|
||||
do_test 1.8.4 {
|
||||
list [file size test.db-wal] [file size test.db-wal2]
|
||||
} {24752 4152}
|
||||
|
||||
do_test 1.8.5 {
|
||||
hexio_write test.db-wal2 [expr 56+16] 0400
|
||||
fix_wal_cksums test.db-wal2
|
||||
} {}
|
||||
|
||||
do_test 1.8.6 {
|
||||
forcecopy test.db test.db2
|
||||
forcecopy test.db-wal test.db2-wal
|
||||
forcecopy test.db-wal2 test.db2-wal2
|
||||
sqlite3 db2 test.db2
|
||||
catchsql { SELECT * FROM sqlite_master } db2
|
||||
} {1 {database disk image is malformed}}
|
||||
db2 close
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
CREATE INDEX t1a ON t1(a);
|
||||
CREATE INDEX t1b ON t1(b);
|
||||
CREATE INDEX t1c ON t1(c);
|
||||
PRAGMA journal_mode = wal2;
|
||||
|
||||
INSERT INTO t1 VALUES(randomblob(50), randomblob(50), randomblob(50));
|
||||
INSERT INTO t1 VALUES(randomblob(50), randomblob(50), randomblob(50));
|
||||
INSERT INTO t1 VALUES(randomblob(50), randomblob(50), randomblob(50));
|
||||
PRAGMA journal_size_limit = 5000;
|
||||
INSERT INTO t1 VALUES(randomblob(50), randomblob(50), randomblob(50));
|
||||
INSERT INTO t1 VALUES(randomblob(50), randomblob(50), randomblob(50));
|
||||
INSERT INTO t1 VALUES(randomblob(50), randomblob(50), randomblob(50));
|
||||
INSERT INTO t1 VALUES(randomblob(50), randomblob(50), randomblob(50));
|
||||
INSERT INTO t1 VALUES(randomblob(50), randomblob(50), randomblob(50));
|
||||
} {wal2 5000}
|
||||
|
||||
do_test 2.1 {
|
||||
forcecopy test.db test.db2
|
||||
forcecopy test.db-wal2 test.db2-wal
|
||||
forcecopy test.db-wal test.db2-wal2
|
||||
|
||||
hexio_write test.db2-wal 5000 1234567890
|
||||
} {5}
|
||||
|
||||
do_test 2.2 {
|
||||
sqlite3 db2 test.db2
|
||||
breakpoint
|
||||
execsql {
|
||||
SELECT count(*) FROM t1;
|
||||
PRAGMA integrity_check
|
||||
} db2
|
||||
} {4 ok}
|
||||
|
||||
do_test 2.3 {
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES(randomblob(50), randomblob(50), randomblob(50));
|
||||
SELECT count(*) FROM t1;
|
||||
PRAGMA integrity_check
|
||||
} db2
|
||||
} {5 ok}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# 2017 September 19
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the operation of the library in
|
||||
# "PRAGMA journal_mode=WAL2" mode.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
|
||||
set testprefix wal2rewrite
|
||||
ifcapable !wal {finish_test ; return }
|
||||
|
||||
proc filesize {filename} {
|
||||
if {[file exists $filename]} {
|
||||
return [file size $filename]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
foreach {tn jrnlmode} {
|
||||
1 wal
|
||||
2 wal2
|
||||
} {
|
||||
reset_db
|
||||
execsql "PRAGMA journal_mode = $jrnlmode"
|
||||
do_execsql_test $tn.1 {
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
PRAGMA cache_size = 5;
|
||||
PRAGMA wal_autocheckpoint = 10;
|
||||
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b INTEGER, c BLOB);
|
||||
CREATE INDEX t1b ON t1(b);
|
||||
CREATE INDEX t1c ON t1(c);
|
||||
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION SELECT i+1 FROM s WHERE i<10
|
||||
)
|
||||
INSERT INTO t1 SELECT i, i, randomblob(800) FROM s;
|
||||
} {10000 10}
|
||||
|
||||
for {set i 0} {$i < 4} {incr i} {
|
||||
do_execsql_test $tn.$i.1 {
|
||||
UPDATE t1 SET c=randomblob(800) WHERE (b%10)==5 AND ($i%2)
|
||||
}
|
||||
do_execsql_test $tn.$i.2 {
|
||||
BEGIN;
|
||||
UPDATE t1 SET b=b+10, c=randomblob(800);
|
||||
UPDATE t1 SET b=b+10, c=randomblob(800);
|
||||
UPDATE t1 SET b=b+10, c=randomblob(800);
|
||||
UPDATE t1 SET b=b+10, c=randomblob(800);
|
||||
UPDATE t1 SET b=b+10, c=randomblob(800);
|
||||
UPDATE t1 SET b=b+10, c=randomblob(800);
|
||||
UPDATE t1 SET b=b+10, c=randomblob(800);
|
||||
UPDATE t1 SET b=b+10, c=randomblob(800);
|
||||
UPDATE t1 SET b=b+10, c=randomblob(800);
|
||||
UPDATE t1 SET b=b+10, c=randomblob(800);
|
||||
}
|
||||
execsql COMMIT
|
||||
|
||||
do_test $tn.$i.3 { expr [filesize test.db-wal] < 100000 } 1
|
||||
do_test $tn.$i.4 { expr [filesize test.db-wal2] < 100000 } 1
|
||||
|
||||
set sum [db eval {SELECT sum(b), md5sum(c) FROM t1}]
|
||||
|
||||
do_test $tn.$i.5 {
|
||||
foreach f [glob -nocomplain test.db2*] {forcedelete $f}
|
||||
foreach f [glob -nocomplain test.db*] {
|
||||
forcecopy $f [string map {test.db test.db2} $f]
|
||||
}
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
db2 eval {SELECT sum(b), md5sum(c) FROM t1}
|
||||
} $sum
|
||||
db2 close
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,62 @@
|
||||
# 2017 September 19
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the operation of the library in
|
||||
# "PRAGMA journal_mode=WAL2" mode.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
|
||||
set testprefix wal2rollback
|
||||
ifcapable !wal {finish_test ; return }
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
CREATE TABLE t2(a, b, c);
|
||||
CREATE INDEX i1 ON t1(a);
|
||||
CREATE INDEX i2 ON t1(b);
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA cache_size = 5;
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s LIMIT 1000
|
||||
)
|
||||
INSERT INTO t1 SELECT i, i, randomblob(200) FROM s;
|
||||
} {wal2 10000}
|
||||
|
||||
do_test 1.1 {
|
||||
expr [file size test.db-wal] > 10000
|
||||
} 1
|
||||
|
||||
do_test 1.2 {
|
||||
execsql {
|
||||
BEGIN;
|
||||
UPDATE t1 SET b=b+1;
|
||||
INSERT INTO t2 VALUES(1,2,3);
|
||||
}
|
||||
expr [file size test.db-wal2] > 10000
|
||||
} {1}
|
||||
|
||||
breakpoint
|
||||
do_execsql_test 1.3 {
|
||||
ROLLBACK;
|
||||
SELECT * FROM t2;
|
||||
SELECT count(*) FROM t1 WHERE a=b;
|
||||
PRAGMA integrity_check;
|
||||
} {1000 ok}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,73 @@
|
||||
# 2018 December 13
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the operation of the library in
|
||||
# "PRAGMA journal_mode=WAL2" mode.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
|
||||
set testprefix wal2savepoint
|
||||
ifcapable !wal {finish_test ; return }
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
CREATE INDEX t1a ON t1(a);
|
||||
CREATE INDEX t1b ON t1(b);
|
||||
CREATE INDEX t1c ON t1(c);
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_size_limit = 15000;
|
||||
PRAGMA wal_autocheckpoint = 0;
|
||||
PRAGMA cache_size = 5;
|
||||
} {wal2 15000 0}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s where i < 200)
|
||||
INSERT INTO t1 SELECT random(), random(), random() FROM s;
|
||||
} {}
|
||||
|
||||
do_test 1.2 {
|
||||
list [file size test.db] [file size test.db-wal] [file size test.db-wal2]
|
||||
} {5120 23088 0}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
BEGIN;
|
||||
SAVEPOINT abc;
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s where i < 100)
|
||||
INSERT INTO t1 SELECT random(), random(), random() FROM s;
|
||||
ROLLBACK TO abc;
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s where i < 10)
|
||||
INSERT INTO t1 SELECT random(), random(), random() FROM s;
|
||||
COMMIT;
|
||||
SELECT count(*) FROM t1;
|
||||
PRAGMA integrity_check;
|
||||
} {210 ok}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
BEGIN;
|
||||
SAVEPOINT abc;
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s where i < 100)
|
||||
INSERT INTO t1 SELECT random(), random(), random() FROM s;
|
||||
ROLLBACK TO abc;
|
||||
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s where i < 10)
|
||||
INSERT INTO t1 SELECT random(), random(), random() FROM s;
|
||||
COMMIT;
|
||||
SELECT count(*) FROM t1;
|
||||
PRAGMA integrity_check;
|
||||
} {220 ok}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,474 @@
|
||||
# 2017 September 19
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the operation of the library in
|
||||
# "PRAGMA journal_mode=WAL2" mode.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
source $testdir/malloc_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
|
||||
set testprefix wal2simple
|
||||
ifcapable !wal {finish_test ; return }
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# The following tests verify that a client can switch in and out of wal
|
||||
# and wal2 mode. But that it is not possible to change directly from wal
|
||||
# to wal2, or from wal2 to wal mode.
|
||||
#
|
||||
do_execsql_test 1.1.0 {
|
||||
PRAGMA journal_mode = wal2
|
||||
} {wal2}
|
||||
execsql { SELECT * FROM sqlite_master}
|
||||
do_execsql_test 1.x {
|
||||
PRAGMA journal_mode;
|
||||
PRAGMA main.journal_mode;
|
||||
} {wal2 wal2}
|
||||
db close
|
||||
do_test 1.1.1 { file size test.db } {1024}
|
||||
do_test 1.1.2 { hexio_read test.db 18 2 } 0303
|
||||
|
||||
sqlite3 db test.db
|
||||
do_execsql_test 1.2.0 {
|
||||
SELECT * FROM sqlite_master;
|
||||
PRAGMA journal_mode = delete;
|
||||
} {delete}
|
||||
db close
|
||||
do_test 1.2.1 { file size test.db } {1024}
|
||||
do_test 1.2.2 { hexio_read test.db 18 2 } 0101
|
||||
|
||||
sqlite3 db test.db
|
||||
do_execsql_test 1.3.0 {
|
||||
SELECT * FROM sqlite_master;
|
||||
PRAGMA journal_mode = wal;
|
||||
} {wal}
|
||||
db close
|
||||
do_test 1.3.1 { file size test.db } {1024}
|
||||
do_test 1.3.2 { hexio_read test.db 18 2 } 0202
|
||||
|
||||
sqlite3 db test.db
|
||||
do_catchsql_test 1.4.0 {
|
||||
PRAGMA journal_mode = wal2;
|
||||
} {1 {cannot change from wal to wal2 mode}}
|
||||
do_execsql_test 1.4.1 {
|
||||
PRAGMA journal_mode = wal;
|
||||
PRAGMA journal_mode = delete;
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_mode = wal2;
|
||||
} {wal delete wal2 wal2}
|
||||
do_catchsql_test 1.4.2 {
|
||||
PRAGMA journal_mode = wal;
|
||||
} {1 {cannot change from wal2 to wal mode}}
|
||||
db close
|
||||
do_test 1.4.3 { hexio_read test.db 18 2 } 0303
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that recovery in wal2 mode works.
|
||||
#
|
||||
forcedelete test.db test.db-wal test.db-wal2
|
||||
reset_db
|
||||
do_execsql_test 2.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_size_limit = 5000;
|
||||
} {wal2 5000}
|
||||
|
||||
proc wal_hook {DB nm nFrame} { $DB eval { PRAGMA wal_checkpoint } }
|
||||
db wal_hook {wal_hook db}
|
||||
|
||||
for {set i 1} {$i <= 200} {incr i} {
|
||||
execsql { INSERT INTO t1 VALUES(NULL, randomblob(100)) }
|
||||
set res [db eval { SELECT sum(a), md5sum(b) FROM t1 }]
|
||||
|
||||
do_test 2.1.$i {
|
||||
foreach f [glob -nocomplain test.db2*] { forcedelete $f }
|
||||
forcecopy test.db test.db2
|
||||
forcecopy test.db-wal test.db2-wal
|
||||
forcecopy test.db-wal2 test.db2-wal2
|
||||
|
||||
sqlite3 db2 test.db2
|
||||
db2 eval { SELECT sum(a), md5sum(b) FROM t1 }
|
||||
} $res
|
||||
|
||||
db2 close
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE t1(x BLOB, y INTEGER PRIMARY KEY);
|
||||
CREATE INDEX i1 ON t1(x);
|
||||
PRAGMA cache_size = 5;
|
||||
PRAGMA journal_mode = wal2;
|
||||
} {wal2}
|
||||
|
||||
do_test 3.1 {
|
||||
execsql BEGIN
|
||||
for {set i 1} {$i < 1000} {incr i} {
|
||||
execsql { INSERT INTO t1 VALUES(randomblob(800), $i) }
|
||||
}
|
||||
execsql COMMIT
|
||||
} {}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
catch { db close }
|
||||
foreach f [glob -nocomplain test.db*] { forcedelete $f }
|
||||
reset_db
|
||||
do_execsql_test 4.0 {
|
||||
CREATE TABLE t1(x, y);
|
||||
PRAGMA journal_mode = wal2;
|
||||
} {wal2}
|
||||
|
||||
do_execsql_test 4.1 {
|
||||
SELECT * FROM t1;
|
||||
} {}
|
||||
|
||||
do_execsql_test 4.2 {
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
} {}
|
||||
|
||||
do_execsql_test 4.3 {
|
||||
SELECT * FROM t1;
|
||||
} {1 2}
|
||||
|
||||
do_test 4.4 {
|
||||
sqlite3 db2 test.db
|
||||
execsql { SELECT * FROM t1 } db2
|
||||
} {1 2}
|
||||
|
||||
do_test 4.5 {
|
||||
lsort [glob test.db*]
|
||||
} {test.db test.db-shm test.db-wal test.db-wal2}
|
||||
|
||||
do_test 4.6 {
|
||||
db close
|
||||
db2 close
|
||||
sqlite3 db test.db
|
||||
execsql { SELECT * FROM t1 }
|
||||
} {1 2}
|
||||
|
||||
do_execsql_test 4.7 {
|
||||
PRAGMA journal_size_limit = 4000;
|
||||
INSERT INTO t1 VALUES(3, 4);
|
||||
INSERT INTO t1 VALUES(5, 6);
|
||||
INSERT INTO t1 VALUES(7, 8);
|
||||
INSERT INTO t1 VALUES(9, 10);
|
||||
INSERT INTO t1 VALUES(11, 12);
|
||||
INSERT INTO t1 VALUES(13, 14);
|
||||
INSERT INTO t1 VALUES(15, 16);
|
||||
INSERT INTO t1 VALUES(17, 18);
|
||||
SELECT * FROM t1;
|
||||
} {4000 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18}
|
||||
|
||||
do_test 4.8 {
|
||||
sqlite3 db2 test.db
|
||||
execsql { SELECT * FROM t1 } db2
|
||||
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18}
|
||||
|
||||
do_test 4.9 {
|
||||
db close
|
||||
db2 close
|
||||
lsort [glob test.db*]
|
||||
} {test.db}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 5.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
|
||||
CREATE INDEX i1 ON t1(b, c);
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_size_limit = 4000;
|
||||
} {wal2 4000}
|
||||
|
||||
proc wal_hook {DB nm nFrame} {
|
||||
$DB eval { PRAGMA wal_checkpoint }
|
||||
}
|
||||
db wal_hook [list wal_hook db]
|
||||
|
||||
|
||||
foreach js {4000 8000 12000} {
|
||||
foreach NROW [list 100 200 300 400 500 600 1000] {
|
||||
do_test 5.$js.$NROW.1 {
|
||||
db eval "DELETE FROM t1"
|
||||
db eval "PRAGMA journal_size_limit = $js"
|
||||
set nTotal 0
|
||||
for {set i 0} {$i < $NROW} {incr i} {
|
||||
db eval { INSERT INTO t1 VALUES($i, $i, randomblob(abs(random()%50))) }
|
||||
incr nTotal $i
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
|
||||
do_test 5.$js.$NROW.2 {
|
||||
sqlite3 db2 test.db
|
||||
db2 eval {
|
||||
PRAGMA integrity_check;
|
||||
SELECT count(*), sum(b) FROM t1;
|
||||
}
|
||||
} [list ok $NROW $nTotal]
|
||||
|
||||
db2 close
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 6.0 {
|
||||
CREATE TABLE tx(x);
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA journal_size_limit = 3500;
|
||||
} {wal2 3500}
|
||||
|
||||
do_test 6.1 {
|
||||
for {set i 0} {$i < 10} {incr i} {
|
||||
execsql "CREATE TABLE t$i (x);"
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test 6.2.1 {
|
||||
foreach f [glob -nocomplain test.db2*] { forcedelete $f }
|
||||
forcecopy test.db-wal2 test.db2-wal2
|
||||
sqlite3 db2 test.db2
|
||||
db2 eval { SELECT * FROM sqlite_master }
|
||||
} {}
|
||||
do_test 6.2.2 {
|
||||
db2 eval {
|
||||
PRAGMA journal_mode = wal2;
|
||||
SELECT * FROM sqlite_master;
|
||||
}
|
||||
} {wal2}
|
||||
|
||||
do_test 6.3.1 {
|
||||
db2 close
|
||||
foreach f [glob -nocomplain test.db2*] { forcedelete $f }
|
||||
forcecopy test.db-wal2 test.db2-wal2
|
||||
forcecopy test.db test.db2
|
||||
sqlite3 db2 test.db2
|
||||
db2 eval { SELECT * FROM sqlite_master }
|
||||
} {table tx tx 2 {CREATE TABLE tx(x)}}
|
||||
do_test 6.3.2 {
|
||||
db2 eval {
|
||||
PRAGMA journal_mode = wal2;
|
||||
SELECT * FROM sqlite_master;
|
||||
}
|
||||
} {wal2 table tx tx 2 {CREATE TABLE tx(x)}}
|
||||
|
||||
do_test 6.4.1 {
|
||||
db2 close
|
||||
foreach f [glob -nocomplain test.db2*] { forcedelete $f }
|
||||
forcecopy test.db-wal2 test.db2-wal2
|
||||
forcecopy test.db-wal test.db2-wal
|
||||
sqlite3 db2 test.db2
|
||||
db2 eval { SELECT * FROM sqlite_master }
|
||||
} {}
|
||||
do_test 6.4.2 {
|
||||
db2 eval {
|
||||
PRAGMA journal_mode = wal2;
|
||||
SELECT * FROM sqlite_master;
|
||||
}
|
||||
} {wal2}
|
||||
db2 close
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
sqlite3 db2 test.db
|
||||
do_execsql_test 7.0 {
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
PRAGMA journal_mode = wal2;
|
||||
PRAGMA wal_autocheckpoint = 0;
|
||||
BEGIN;
|
||||
CREATE TABLE t1(a);
|
||||
INSERT INTO t1 VALUES( randomblob(8000) );
|
||||
COMMIT;
|
||||
} {10000 wal2 0}
|
||||
|
||||
do_test 7.1 {
|
||||
list [file size test.db-wal] [file size test.db-wal2]
|
||||
} {9464 0}
|
||||
|
||||
# Connection db2 is holding a PART1 lock.
|
||||
#
|
||||
# 7.2.2: Test that the PART1 does not prevent db from switching to the
|
||||
# other wal file.
|
||||
#
|
||||
# 7.2.3: Test that the PART1 does prevent a checkpoint of test.db-wal.
|
||||
#
|
||||
# 7.2.4: Test that after the PART1 is released the checkpoint is possible.
|
||||
#
|
||||
do_test 7.2.1 {
|
||||
execsql {
|
||||
BEGIN;
|
||||
SELECT count(*) FROM t1;
|
||||
} db2
|
||||
} {1}
|
||||
do_test 7.2.2 {
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES( randomblob(800) );
|
||||
INSERT INTO t1 VALUES( randomblob(800) );
|
||||
}
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {13656 3176 1024}
|
||||
do_test 7.2.3 {
|
||||
execsql { PRAGMA wal_checkpoint }
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {13656 3176 1024}
|
||||
do_test 7.2.4 {
|
||||
execsql { END } db2
|
||||
execsql { PRAGMA wal_checkpoint }
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {13656 3176 11264}
|
||||
|
||||
# Connection db2 is holding a PART2_FULL1 lock.
|
||||
#
|
||||
# 7.3.2: Test that the lock does not prevent checkpointing.
|
||||
#
|
||||
# 7.3.3: Test that the lock does prevent the writer from overwriting
|
||||
# test.db-wal.
|
||||
#
|
||||
# 7.3.4: Test that after the PART2_FULL1 is released the writer can
|
||||
# switch wal files and overwrite test.db-wal
|
||||
#
|
||||
db close
|
||||
db2 close
|
||||
sqlite3 db test.db
|
||||
sqlite3 db2 test.db
|
||||
do_test 7.3.1 {
|
||||
execsql {
|
||||
PRAGMA wal_autocheckpoint = 0;
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
INSERT INTO t1 VALUES(randomblob(10000));
|
||||
INSERT INTO t1 VALUES(randomblob(500));
|
||||
}
|
||||
execsql {
|
||||
BEGIN;
|
||||
SELECT count(*) FROM t1;
|
||||
} db2
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {12608 3176 11264}
|
||||
do_test 7.3.2 {
|
||||
execsql { PRAGMA wal_checkpoint }
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {12608 3176 21504}
|
||||
do_test 7.3.3 {
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES(randomblob(10000));
|
||||
INSERT INTO t1 VALUES(randomblob(500));
|
||||
}
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {12608 18896 21504}
|
||||
do_test 7.3.4 {
|
||||
execsql END db2
|
||||
execsql { INSERT INTO t1 VALUES(randomblob(5000)); }
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {12608 18896 21504}
|
||||
|
||||
# Connection db2 is holding a PART2 lock.
|
||||
#
|
||||
# 7.4.2: Test that the lock does not prevent writer switching to test.db-wal.
|
||||
#
|
||||
# 7.3.3: Test that the lock does prevent checkpointing of test.db-wal2.
|
||||
#
|
||||
# 7.3.4: Test that after the PART2 is released test.db-wal2 can be
|
||||
# checkpointed.
|
||||
#
|
||||
db close
|
||||
db2 close
|
||||
sqlite3 db test.db
|
||||
sqlite3 db2 test.db
|
||||
do_test 7.4.1 {
|
||||
execsql {
|
||||
PRAGMA wal_autocheckpoint = 0;
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
INSERT INTO t1 VALUES(randomblob(10000));
|
||||
INSERT INTO t1 VALUES(randomblob(10000));
|
||||
PRAGMA wal_checkpoint;
|
||||
}
|
||||
execsql {
|
||||
BEGIN;
|
||||
SELECT count(*) FROM t1;
|
||||
} db2
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {12608 12608 44032}
|
||||
do_test 7.4.2 {
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES(randomblob(5000));
|
||||
}
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {12608 12608 44032}
|
||||
do_test 7.4.3 {
|
||||
execsql { PRAGMA wal_checkpoint }
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {12608 12608 44032}
|
||||
do_test 7.4.4 {
|
||||
execsql END db2
|
||||
execsql { PRAGMA wal_checkpoint }
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {12608 12608 54272}
|
||||
|
||||
# Connection db2 is holding a PART1_FULL2 lock.
|
||||
#
|
||||
# 7.5.2: Test that the lock does not prevent a checkpoint of test.db-wal2.
|
||||
#
|
||||
# 7.5.3: Test that the lock does prevent the writer from overwriting
|
||||
# test.db-wal2.
|
||||
#
|
||||
# 7.5.4: Test that after the PART1_FULL2 lock is released, the writer
|
||||
# can switch to test.db-wal2.
|
||||
#
|
||||
db close
|
||||
db2 close
|
||||
sqlite3 db test.db
|
||||
sqlite3 db2 test.db
|
||||
do_test 7.5.1 {
|
||||
execsql {
|
||||
PRAGMA wal_autocheckpoint = 0;
|
||||
PRAGMA journal_size_limit = 10000;
|
||||
INSERT INTO t1 VALUES(randomblob(10000));
|
||||
INSERT INTO t1 VALUES(randomblob(10000));
|
||||
PRAGMA wal_checkpoint;
|
||||
INSERT INTO t1 VALUES(randomblob(5000));
|
||||
}
|
||||
execsql {
|
||||
BEGIN;
|
||||
SELECT count(*) FROM t1;
|
||||
} db2
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {12608 12608 64512}
|
||||
do_test 7.5.2 {
|
||||
execsql { PRAGMA wal_checkpoint }
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {12608 12608 75776}
|
||||
do_test 7.5.3.1 {
|
||||
execsql { INSERT INTO t1 VALUES(randomblob(5000)) }
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {14704 12608 75776}
|
||||
do_test 7.5.3.2 {
|
||||
execsql { INSERT INTO t1 VALUES(randomblob(5000)) }
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {22040 12608 75776}
|
||||
do_test 7.5.4 {
|
||||
execsql END db2
|
||||
execsql { INSERT INTO t1 VALUES(randomblob(5000)) }
|
||||
list [file size test.db-wal] [file size test.db-wal2] [file size test.db]
|
||||
} {22040 12608 75776}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# 2018 December 5
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the operation of the library in
|
||||
# "PRAGMA journal_mode=WAL2" mode.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
set testprefix wal2snapshot
|
||||
ifcapable !wal {finish_test ; return }
|
||||
ifcapable !snapshot {finish_test; return}
|
||||
|
||||
foreach {tn mode} {1 wal 2 wal2} {
|
||||
reset_db
|
||||
do_execsql_test $tn.1 "PRAGMA journal_mode = $mode" $mode
|
||||
|
||||
do_execsql_test $tn.2 {
|
||||
CREATE TABLE t1(a, b);
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(3, 4);
|
||||
BEGIN;
|
||||
}
|
||||
|
||||
# Check that sqlite3_snapshot_get() is an error for a wal2 db.
|
||||
#
|
||||
if {$tn==1} {
|
||||
do_test 1.3 {
|
||||
set S [sqlite3_snapshot_get db main]
|
||||
sqlite3_snapshot_free $S
|
||||
} {}
|
||||
} else {
|
||||
do_test 2.3 {
|
||||
list [catch { sqlite3_snapshot_get db main } msg] $msg
|
||||
} {1 SQLITE_ERROR}
|
||||
}
|
||||
|
||||
# Check that sqlite3_snapshot_recover() is an error for a wal2 db.
|
||||
#
|
||||
do_execsql_test $tn.4 COMMIT
|
||||
if {$tn==1} {
|
||||
do_test 1.5 {
|
||||
sqlite3_snapshot_recover db main
|
||||
} {}
|
||||
} else {
|
||||
do_test 2.5 {
|
||||
list [catch { sqlite3_snapshot_recover db main } msg] $msg
|
||||
} {1 SQLITE_ERROR}
|
||||
}
|
||||
|
||||
# Check that sqlite3_snapshot_open() is an error for a wal2 db.
|
||||
#
|
||||
if {$tn==1} {
|
||||
do_test 1.6 {
|
||||
execsql BEGIN
|
||||
set SNAPSHOT [sqlite3_snapshot_get_blob db main]
|
||||
sqlite3_snapshot_open_blob db main $SNAPSHOT
|
||||
execsql COMMIT
|
||||
} {}
|
||||
} else {
|
||||
|
||||
do_test 2.6.1 {
|
||||
execsql BEGIN
|
||||
set res [
|
||||
list [catch { sqlite3_snapshot_open_blob db main $SNAPSHOT } msg] $msg
|
||||
]
|
||||
execsql COMMIT
|
||||
set res
|
||||
} {1 SQLITE_ERROR}
|
||||
do_test 2.6.2 {
|
||||
execsql BEGIN
|
||||
execsql {SELECT * FROM sqlite_master}
|
||||
set res [
|
||||
list [catch { sqlite3_snapshot_open_blob db main $SNAPSHOT } msg] $msg
|
||||
]
|
||||
execsql COMMIT
|
||||
set res
|
||||
} {1 SQLITE_ERROR}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -90,4 +90,44 @@ proc wal_fix_walindex_cksum {hdrvar} {
|
||||
lset hdr 11 $c2
|
||||
}
|
||||
|
||||
# This command assumes that $file is the name of a database file opened
|
||||
# in wal mode using a [testvfs] VFS. It returns a list of the 12 32-bit
|
||||
# integers that make up the wal-index-header for the named file.
|
||||
#
|
||||
proc set_tvfs_hdr {file args} {
|
||||
|
||||
# Set $nHdr to the number of bytes in the wal-index header:
|
||||
set nHdr 48
|
||||
set nInt [expr {$nHdr/4}]
|
||||
|
||||
if {[llength $args]>2} {
|
||||
error {wrong # args: should be "set_tvfs_hdr fileName ?val1? ?val2?"}
|
||||
}
|
||||
|
||||
set blob [tvfs shm $file]
|
||||
if {$::tcl_platform(byteOrder)=="bigEndian"} {set fmt I} {set fmt i}
|
||||
|
||||
if {[llength $args]} {
|
||||
set ia [lindex $args 0]
|
||||
set ib $ia
|
||||
if {[llength $args]==2} {
|
||||
set ib [lindex $args 1]
|
||||
}
|
||||
binary scan $blob a[expr $nHdr*2]a* dummy tail
|
||||
set blob [binary format ${fmt}${nInt}${fmt}${nInt}a* $ia $ib $tail]
|
||||
tvfs shm $file $blob
|
||||
}
|
||||
|
||||
binary scan $blob ${fmt}${nInt} ints
|
||||
return $ints
|
||||
}
|
||||
|
||||
proc incr_tvfs_hdr {file idx incrval} {
|
||||
set ints [set_tvfs_hdr $file]
|
||||
set v [lindex $ints $idx]
|
||||
incr v $incrval
|
||||
lset ints $idx $v
|
||||
set_tvfs_hdr $file $ints
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ proc lock_callback {method filename handle lock} {
|
||||
db2 eval { INSERT INTO x VALUES('x') }
|
||||
}
|
||||
}
|
||||
db timeout 10
|
||||
db timeout 1100
|
||||
do_catchsql_test 2.4 {
|
||||
BEGIN EXCLUSIVE;
|
||||
} {0 {}}
|
||||
|
||||
@@ -32,6 +32,7 @@ set extras {
|
||||
UPLUS
|
||||
TRUTH
|
||||
REGISTER
|
||||
CONCURRENT
|
||||
VECTOR
|
||||
SELECT_COLUMN
|
||||
IF_NULL_ROW
|
||||
|
||||
@@ -204,7 +204,6 @@ static Keyword aKeywordTable[] = {
|
||||
{ "ELSE", "TK_ELSE", ALWAYS },
|
||||
{ "ESCAPE", "TK_ESCAPE", ALWAYS },
|
||||
{ "EXCEPT", "TK_EXCEPT", COMPOUND },
|
||||
{ "EXCLUSIVE", "TK_EXCLUSIVE", ALWAYS },
|
||||
{ "EXISTS", "TK_EXISTS", ALWAYS },
|
||||
{ "EXPLAIN", "TK_EXPLAIN", EXPLAIN },
|
||||
{ "FAIL", "TK_FAIL", CONFLICT|TRIGGER },
|
||||
|
||||
+636
@@ -0,0 +1,636 @@
|
||||
/*
|
||||
** 2017 June 7
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** Simple multi-threaded server used for informal testing of concurrency
|
||||
** between connections in different threads. Listens for tcp/ip connections
|
||||
** on port 9999 of the 127.0.0.1 interface only. To build:
|
||||
**
|
||||
** gcc -g $(TOP)/tool/tserver.c sqlite3.o -lpthread -o tserver
|
||||
**
|
||||
** To run using "x.db" as the db file:
|
||||
**
|
||||
** ./tserver x.db
|
||||
**
|
||||
** To connect, open a client socket on port 9999 and start sending commands.
|
||||
** Commands are either SQL - which must be terminated by a semi-colon, or
|
||||
** dot-commands, which must be terminated by a newline. If an SQL statement
|
||||
** is seen, it is prepared and added to an internal list.
|
||||
**
|
||||
** Dot-commands are:
|
||||
**
|
||||
** .list Display all SQL statements in the list.
|
||||
** .quit Disconnect.
|
||||
** .run Run all SQL statements in the list.
|
||||
** .repeats N Configure the number of repeats per ".run".
|
||||
** .seconds N Configure the number of seconds to ".run" for.
|
||||
** .mutex_commit Add a "COMMIT" protected by a g.commit_mutex
|
||||
** to the current SQL.
|
||||
** .stop Stop the tserver process - exit(0).
|
||||
** .checkpoint N
|
||||
** .integrity_check
|
||||
**
|
||||
** Example input:
|
||||
**
|
||||
** BEGIN;
|
||||
** INSERT INTO t1 VALUES(randomblob(10), randomblob(100));
|
||||
** INSERT INTO t1 VALUES(randomblob(10), randomblob(100));
|
||||
** INSERT INTO t1 VALUES(randomblob(10), randomblob(100));
|
||||
** COMMIT;
|
||||
** .repeats 100000
|
||||
** .run
|
||||
**
|
||||
*/
|
||||
#define TSERVER_PORTNUMBER 9999
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
#define TSERVER_DEFAULT_CHECKPOINT_THRESHOLD 3900
|
||||
|
||||
/* Global variables */
|
||||
struct TserverGlobal {
|
||||
char *zDatabaseName; /* Database used by this server */
|
||||
char *zVfs;
|
||||
sqlite3_mutex *commit_mutex;
|
||||
sqlite3 *db; /* Global db handle */
|
||||
|
||||
/* The following use native pthreads instead of a portable interface. This
|
||||
** is because a condition variable, as well as a mutex, is required. */
|
||||
pthread_mutex_t ckpt_mutex;
|
||||
pthread_cond_t ckpt_cond;
|
||||
int nThreshold; /* Checkpoint when wal is this large */
|
||||
int bCkptRequired; /* True if wal checkpoint is required */
|
||||
int nRun; /* Number of clients in ".run" */
|
||||
int nWait; /* Number of clients waiting on ckpt_cond */
|
||||
};
|
||||
|
||||
static struct TserverGlobal g = {0};
|
||||
|
||||
typedef struct ClientSql ClientSql;
|
||||
struct ClientSql {
|
||||
sqlite3_stmt *pStmt;
|
||||
int flags;
|
||||
};
|
||||
|
||||
#define TSERVER_CLIENTSQL_MUTEX 0x0001
|
||||
#define TSERVER_CLIENTSQL_INTEGRITY 0x0002
|
||||
|
||||
typedef struct ClientCtx ClientCtx;
|
||||
struct ClientCtx {
|
||||
sqlite3 *db; /* Database handle for this client */
|
||||
int fd; /* Client fd */
|
||||
int nRepeat; /* Number of times to repeat SQL */
|
||||
int nSecond; /* Number of seconds to run for */
|
||||
ClientSql *aPrepare; /* Array of prepared statements */
|
||||
int nPrepare; /* Valid size of apPrepare[] */
|
||||
int nAlloc; /* Allocated size of apPrepare[] */
|
||||
|
||||
int nClientThreshold; /* Threshold for checkpointing */
|
||||
int bClientCkptRequired; /* True to do a checkpoint */
|
||||
};
|
||||
|
||||
static int is_eol(int i){
|
||||
return (i=='\n' || i=='\r');
|
||||
}
|
||||
static int is_whitespace(int i){
|
||||
return (i==' ' || i=='\t' || is_eol(i));
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of SQL scalar function usleep().
|
||||
*/
|
||||
static void usleepFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
int nUs;
|
||||
sqlite3_vfs *pVfs = (sqlite3_vfs*)sqlite3_user_data(context);
|
||||
assert( argc==1 );
|
||||
nUs = sqlite3_value_int64(argv[0]);
|
||||
pVfs->xSleep(pVfs, nUs);
|
||||
}
|
||||
|
||||
static void trim_string(const char **pzStr, int *pnStr){
|
||||
const char *zStr = *pzStr;
|
||||
int nStr = *pnStr;
|
||||
|
||||
while( nStr>0 && is_whitespace(zStr[0]) ){
|
||||
zStr++;
|
||||
nStr--;
|
||||
}
|
||||
while( nStr>0 && is_whitespace(zStr[nStr-1]) ){
|
||||
nStr--;
|
||||
}
|
||||
|
||||
*pzStr = zStr;
|
||||
*pnStr = nStr;
|
||||
}
|
||||
|
||||
static int send_message(ClientCtx *p, const char *zFmt, ...){
|
||||
char *zMsg;
|
||||
va_list ap; /* Vararg list */
|
||||
va_start(ap, zFmt);
|
||||
int res = -1;
|
||||
|
||||
zMsg = sqlite3_vmprintf(zFmt, ap);
|
||||
if( zMsg ){
|
||||
res = write(p->fd, zMsg, strlen(zMsg));
|
||||
}
|
||||
sqlite3_free(zMsg);
|
||||
va_end(ap);
|
||||
|
||||
return (res<0);
|
||||
}
|
||||
|
||||
static int handle_some_sql(ClientCtx *p, const char *zSql, int nSql){
|
||||
const char *zTail = zSql;
|
||||
int nTail = nSql;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
while( rc==SQLITE_OK ){
|
||||
if( p->nPrepare>=p->nAlloc ){
|
||||
int nByte = (p->nPrepare+32) * sizeof(ClientSql);
|
||||
ClientSql *aNew = sqlite3_realloc(p->aPrepare, nByte);
|
||||
if( aNew ){
|
||||
memset(&aNew[p->nPrepare], 0, sizeof(ClientSql)*32);
|
||||
p->aPrepare = aNew;
|
||||
p->nAlloc = p->nPrepare+32;
|
||||
}else{
|
||||
rc = SQLITE_NOMEM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
rc = sqlite3_prepare_v2(
|
||||
p->db, zTail, nTail, &p->aPrepare[p->nPrepare].pStmt, &zTail
|
||||
);
|
||||
if( rc!=SQLITE_OK ){
|
||||
send_message(p, "error - %s (eec=%d)\n", sqlite3_errmsg(p->db),
|
||||
sqlite3_extended_errcode(p->db)
|
||||
);
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
if( p->aPrepare[p->nPrepare].pStmt==0 ){
|
||||
break;
|
||||
}
|
||||
p->nPrepare++;
|
||||
nTail = nSql - (zTail-zSql);
|
||||
rc = send_message(p, "ok (%d SQL statements)\n", p->nPrepare);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static sqlite3_int64 get_timer(void){
|
||||
struct timeval t;
|
||||
gettimeofday(&t, 0);
|
||||
return ((sqlite3_int64)t.tv_usec / 1000) + ((sqlite3_int64)t.tv_sec * 1000);
|
||||
}
|
||||
|
||||
static void clear_sql(ClientCtx *p){
|
||||
int j;
|
||||
for(j=0; j<p->nPrepare; j++){
|
||||
sqlite3_finalize(p->aPrepare[j].pStmt);
|
||||
}
|
||||
p->nPrepare = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** The sqlite3_wal_hook() callback used by all client database connections.
|
||||
*/
|
||||
static int clientWalHook(void *pArg, sqlite3 *db, const char *zDb, int nFrame){
|
||||
if( g.nThreshold>0 ){
|
||||
if( nFrame>=g.nThreshold ){
|
||||
g.bCkptRequired = 1;
|
||||
}
|
||||
}else{
|
||||
ClientCtx *pCtx = (ClientCtx*)pArg;
|
||||
if( pCtx->nClientThreshold && nFrame>=pCtx->nClientThreshold ){
|
||||
pCtx->bClientCkptRequired = 1;
|
||||
}
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static int handle_run_command(ClientCtx *p){
|
||||
int i, j;
|
||||
int nBusy = 0;
|
||||
sqlite3_int64 t0 = get_timer();
|
||||
sqlite3_int64 t1 = t0;
|
||||
int nT1 = 0;
|
||||
int nTBusy1 = 0;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
pthread_mutex_lock(&g.ckpt_mutex);
|
||||
g.nRun++;
|
||||
pthread_mutex_unlock(&g.ckpt_mutex);
|
||||
|
||||
|
||||
for(j=0; (p->nRepeat<=0 || j<p->nRepeat) && rc==SQLITE_OK; j++){
|
||||
sqlite3_int64 t2;
|
||||
|
||||
for(i=0; i<p->nPrepare && rc==SQLITE_OK; i++){
|
||||
sqlite3_stmt *pStmt = p->aPrepare[i].pStmt;
|
||||
|
||||
/* If the MUTEX flag is set, grab g.commit_mutex before executing
|
||||
** the SQL statement (which is always "COMMIT" in this case). */
|
||||
if( p->aPrepare[i].flags & TSERVER_CLIENTSQL_MUTEX ){
|
||||
sqlite3_mutex_enter(g.commit_mutex);
|
||||
}
|
||||
|
||||
/* Execute the statement */
|
||||
if( p->aPrepare[i].flags & TSERVER_CLIENTSQL_INTEGRITY ){
|
||||
sqlite3_step(pStmt);
|
||||
if( sqlite3_stricmp("ok", sqlite3_column_text(pStmt, 0)) ){
|
||||
send_message(p, "error - integrity_check failed: %s\n",
|
||||
sqlite3_column_text(pStmt, 0)
|
||||
);
|
||||
}
|
||||
sqlite3_reset(pStmt);
|
||||
}
|
||||
while( sqlite3_step(pStmt)==SQLITE_ROW );
|
||||
rc = sqlite3_reset(pStmt);
|
||||
|
||||
/* Relinquish the g.commit_mutex mutex if required. */
|
||||
if( p->aPrepare[i].flags & TSERVER_CLIENTSQL_MUTEX ){
|
||||
sqlite3_mutex_leave(g.commit_mutex);
|
||||
}
|
||||
|
||||
if( (rc & 0xFF)==SQLITE_BUSY ){
|
||||
if( sqlite3_get_autocommit(p->db)==0 ){
|
||||
sqlite3_exec(p->db, "ROLLBACK", 0, 0, 0);
|
||||
}
|
||||
nBusy++;
|
||||
rc = SQLITE_OK;
|
||||
break;
|
||||
}
|
||||
else if( rc!=SQLITE_OK ){
|
||||
send_message(p, "error - %s (eec=%d)\n", sqlite3_errmsg(p->db),
|
||||
sqlite3_extended_errcode(p->db)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
t2 = get_timer();
|
||||
if( t2>=(t1+1000) ){
|
||||
int nMs = (t2 - t1);
|
||||
int nDone = (j+1 - nBusy - nT1);
|
||||
|
||||
rc = send_message(
|
||||
p, "(%d done @ %d per second, %d busy)\n",
|
||||
nDone, (1000*nDone + nMs/2) / nMs, nBusy - nTBusy1
|
||||
);
|
||||
t1 = t2;
|
||||
nT1 = j+1 - nBusy;
|
||||
nTBusy1 = nBusy;
|
||||
if( p->nSecond>0 && (p->nSecond*1000)<=t1-t0 ) break;
|
||||
}
|
||||
|
||||
/* Global checkpoint handling. */
|
||||
if( g.nThreshold>0 ){
|
||||
pthread_mutex_lock(&g.ckpt_mutex);
|
||||
if( rc==SQLITE_OK && g.bCkptRequired ){
|
||||
if( g.nWait==g.nRun-1 ){
|
||||
/* All other clients are already waiting on the condition variable.
|
||||
** Run the checkpoint, signal the condition and move on. */
|
||||
rc = sqlite3_wal_checkpoint(p->db, "main");
|
||||
g.bCkptRequired = 0;
|
||||
pthread_cond_broadcast(&g.ckpt_cond);
|
||||
}else{
|
||||
assert( g.nWait<g.nRun-1 );
|
||||
g.nWait++;
|
||||
pthread_cond_wait(&g.ckpt_cond, &g.ckpt_mutex);
|
||||
g.nWait--;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g.ckpt_mutex);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK && p->bClientCkptRequired ){
|
||||
rc = sqlite3_wal_checkpoint(p->db, "main");
|
||||
if( rc==SQLITE_BUSY ) rc = SQLITE_OK;
|
||||
assert( rc==SQLITE_OK );
|
||||
p->bClientCkptRequired = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
int nMs = (int)(get_timer() - t0);
|
||||
send_message(p, "ok (%d/%d SQLITE_BUSY)\n", nBusy, j);
|
||||
if( p->nRepeat<=0 ){
|
||||
send_message(p, "### ok %d busy %d ms %d\n", j-nBusy, nBusy, nMs);
|
||||
}
|
||||
}
|
||||
clear_sql(p);
|
||||
|
||||
pthread_mutex_lock(&g.ckpt_mutex);
|
||||
g.nRun--;
|
||||
pthread_mutex_unlock(&g.ckpt_mutex);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int handle_dot_command(ClientCtx *p, const char *zCmd, int nCmd){
|
||||
int n;
|
||||
int rc = 0;
|
||||
const char *z = &zCmd[1];
|
||||
const char *zArg;
|
||||
int nArg;
|
||||
|
||||
assert( zCmd[0]=='.' );
|
||||
for(n=0; n<(nCmd-1); n++){
|
||||
if( is_whitespace(z[n]) ) break;
|
||||
}
|
||||
|
||||
zArg = &z[n];
|
||||
nArg = nCmd-n;
|
||||
trim_string(&zArg, &nArg);
|
||||
|
||||
if( n>=1 && n<=4 && 0==strncmp(z, "list", n) ){
|
||||
int i;
|
||||
for(i=0; rc==0 && i<p->nPrepare; i++){
|
||||
const char *zSql = sqlite3_sql(p->aPrepare[i].pStmt);
|
||||
int nSql = strlen(zSql);
|
||||
trim_string(&zSql, &nSql);
|
||||
rc = send_message(p, "%d: %.*s\n", i, nSql, zSql);
|
||||
}
|
||||
}
|
||||
|
||||
else if( n>=1 && n<=4 && 0==strncmp(z, "quit", n) ){
|
||||
rc = -1;
|
||||
}
|
||||
|
||||
else if( n>=2 && n<=7 && 0==strncmp(z, "repeats", n) ){
|
||||
if( nArg ){
|
||||
p->nRepeat = strtol(zArg, 0, 0);
|
||||
if( p->nRepeat>0 ) p->nSecond = 0;
|
||||
}
|
||||
rc = send_message(p, "ok (repeat=%d)\n", p->nRepeat);
|
||||
}
|
||||
|
||||
else if( n>=2 && n<=3 && 0==strncmp(z, "run", n) ){
|
||||
rc = handle_run_command(p);
|
||||
}
|
||||
|
||||
else if( n>=2 && n<=7 && 0==strncmp(z, "seconds", n) ){
|
||||
if( nArg ){
|
||||
p->nSecond = strtol(zArg, 0, 0);
|
||||
if( p->nSecond>0 ) p->nRepeat = 0;
|
||||
}
|
||||
rc = send_message(p, "ok (seconds=%d)\n", p->nSecond);
|
||||
}
|
||||
|
||||
else if( n>=1 && n<=12 && 0==strncmp(z, "mutex_commit", n) ){
|
||||
rc = handle_some_sql(p, "COMMIT;", 7);
|
||||
if( rc==SQLITE_OK ){
|
||||
p->aPrepare[p->nPrepare-1].flags |= TSERVER_CLIENTSQL_MUTEX;
|
||||
}
|
||||
}
|
||||
|
||||
else if( n>=1 && n<=10 && 0==strncmp(z, "checkpoint", n) ){
|
||||
if( nArg ){
|
||||
p->nClientThreshold = strtol(zArg, 0, 0);
|
||||
}
|
||||
rc = send_message(p, "ok (checkpoint=%d)\n", p->nClientThreshold);
|
||||
}
|
||||
|
||||
else if( n>=2 && n<=4 && 0==strncmp(z, "stop", n) ){
|
||||
sqlite3_close(g.db);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
else if( n>=2 && n<=15 && 0==strncmp(z, "integrity_check", n) ){
|
||||
rc = handle_some_sql(p, "PRAGMA integrity_check;", 23);
|
||||
if( rc==SQLITE_OK ){
|
||||
p->aPrepare[p->nPrepare-1].flags |= TSERVER_CLIENTSQL_INTEGRITY;
|
||||
}
|
||||
}
|
||||
|
||||
else{
|
||||
send_message(p,
|
||||
"unrecognized dot command: %.*s\n"
|
||||
"should be \"list\", \"run\", \"repeats\", \"mutex_commit\", "
|
||||
"\"checkpoint\", \"integrity_check\" or \"seconds\"\n", n, z
|
||||
);
|
||||
rc = 1;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void *handle_client(void *pArg){
|
||||
char zCmd[32*1024]; /* Read buffer */
|
||||
int nCmd = 0; /* Valid bytes in zCmd[] */
|
||||
int res; /* Result of read() call */
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
ClientCtx ctx;
|
||||
memset(&ctx, 0, sizeof(ClientCtx));
|
||||
|
||||
ctx.fd = (int)(intptr_t)pArg;
|
||||
ctx.nRepeat = 1;
|
||||
rc = sqlite3_open_v2(g.zDatabaseName, &ctx.db,
|
||||
SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, g.zVfs
|
||||
);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3_open(): %s\n", sqlite3_errmsg(ctx.db));
|
||||
return 0;
|
||||
}
|
||||
sqlite3_create_function(
|
||||
ctx.db, "usleep", 1, SQLITE_UTF8, (void*)sqlite3_vfs_find(0),
|
||||
usleepFunc, 0, 0
|
||||
);
|
||||
|
||||
/* Register the wal-hook with the new client connection */
|
||||
sqlite3_wal_hook(ctx.db, clientWalHook, (void*)&ctx);
|
||||
|
||||
while( rc==SQLITE_OK ){
|
||||
int i;
|
||||
int iStart;
|
||||
int nConsume;
|
||||
res = read(ctx.fd, &zCmd[nCmd], sizeof(zCmd)-nCmd-1);
|
||||
if( res<=0 ) break;
|
||||
nCmd += res;
|
||||
if( nCmd>=sizeof(zCmd)-1 ){
|
||||
fprintf(stderr, "oversized (>32KiB) message\n");
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
zCmd[nCmd] = '\0';
|
||||
|
||||
do {
|
||||
nConsume = 0;
|
||||
|
||||
/* Gobble up any whitespace */
|
||||
iStart = 0;
|
||||
while( is_whitespace(zCmd[iStart]) ) iStart++;
|
||||
|
||||
if( zCmd[iStart]=='.' ){
|
||||
/* This is a dot-command. Search for end-of-line. */
|
||||
for(i=iStart; i<nCmd; i++){
|
||||
if( is_eol(zCmd[i]) ){
|
||||
rc = handle_dot_command(&ctx, &zCmd[iStart], i-iStart);
|
||||
nConsume = i+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
|
||||
int iSemi;
|
||||
char c = 0;
|
||||
for(iSemi=iStart; iSemi<nCmd; iSemi++){
|
||||
if( zCmd[iSemi]==';' ){
|
||||
c = zCmd[iSemi+1];
|
||||
zCmd[iSemi+1] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( iSemi<nCmd ){
|
||||
if( sqlite3_complete(zCmd) ){
|
||||
rc = handle_some_sql(&ctx, zCmd, iSemi+1);
|
||||
nConsume = iSemi+1;
|
||||
}
|
||||
|
||||
if( c ){
|
||||
zCmd[iSemi+1] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( nConsume>0 ){
|
||||
nCmd = nCmd-nConsume;
|
||||
if( nCmd>0 ){
|
||||
memmove(zCmd, &zCmd[nConsume], nCmd);
|
||||
}
|
||||
}
|
||||
}while( rc==SQLITE_OK && nConsume>0 );
|
||||
}
|
||||
|
||||
fprintf(stdout, "Client %d disconnects (rc=%d)\n", ctx.fd, rc);
|
||||
fflush(stdout);
|
||||
close(ctx.fd);
|
||||
clear_sql(&ctx);
|
||||
sqlite3_free(ctx.aPrepare);
|
||||
sqlite3_close(ctx.db);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usage(const char *zExec){
|
||||
fprintf(stderr, "Usage: %s ?-vfs VFS? DATABASE\n", zExec);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int sfd;
|
||||
int rc;
|
||||
int yes = 1;
|
||||
struct sockaddr_in server;
|
||||
int i;
|
||||
|
||||
/* Ignore SIGPIPE. Otherwise the server exits if a client disconnects
|
||||
** abruptly. */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
g.nThreshold = TSERVER_DEFAULT_CHECKPOINT_THRESHOLD;
|
||||
if( (argc%2) ) usage(argv[0]);
|
||||
for(i=1; i<(argc-1); i+=2){
|
||||
int n = strlen(argv[i]);
|
||||
if( n>=2 && 0==sqlite3_strnicmp("-walautocheckpoint", argv[i], n) ){
|
||||
g.nThreshold = strtol(argv[i+1], 0, 0);
|
||||
}else
|
||||
if( n>=2 && 0==sqlite3_strnicmp("-vfs", argv[i], n) ){
|
||||
g.zVfs = argv[i+1];
|
||||
}
|
||||
}
|
||||
g.zDatabaseName = argv[argc-1];
|
||||
|
||||
g.commit_mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
|
||||
pthread_mutex_init(&g.ckpt_mutex, 0);
|
||||
pthread_cond_init(&g.ckpt_cond, 0);
|
||||
|
||||
rc = sqlite3_open_v2(g.zDatabaseName, &g.db,
|
||||
SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, g.zVfs
|
||||
);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3_open(): %s\n", sqlite3_errmsg(g.db));
|
||||
return 1;
|
||||
}
|
||||
|
||||
rc = sqlite3_exec(g.db, "SELECT * FROM sqlite_master", 0, 0, 0);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3_exec(): %s\n", sqlite3_errmsg(g.db));
|
||||
return 1;
|
||||
}
|
||||
|
||||
sfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if( sfd<0 ){
|
||||
fprintf(stderr, "socket() failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||
if( rc<0 ){
|
||||
perror("setsockopt");
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(&server, 0, sizeof(server));
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
server.sin_port = htons(TSERVER_PORTNUMBER);
|
||||
|
||||
rc = bind(sfd, (struct sockaddr *)&server, sizeof(struct sockaddr));
|
||||
if( rc<0 ){
|
||||
fprintf(stderr, "bind() failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
rc = listen(sfd, 8);
|
||||
if( rc<0 ){
|
||||
fprintf(stderr, "listen() failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while( 1 ){
|
||||
pthread_t tid;
|
||||
int cfd = accept(sfd, NULL, NULL);
|
||||
if( cfd<0 ){
|
||||
perror("accept()");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stdout, "Client %d connects\n", cfd);
|
||||
fflush(stdout);
|
||||
rc = pthread_create(&tid, NULL, handle_client, (void*)(intptr_t)cfd);
|
||||
if( rc!=0 ){
|
||||
perror("pthread_create()");
|
||||
return 1;
|
||||
}
|
||||
|
||||
pthread_detach(tid);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
#!/usr/bin/tclsh
|
||||
#
|
||||
# This script is used to run the performance test cases described in
|
||||
# README-server-edition.html.
|
||||
#
|
||||
|
||||
|
||||
package require sqlite3
|
||||
|
||||
# Default values for command line switches:
|
||||
set O(-database) ""
|
||||
set O(-rows) [expr 5000000]
|
||||
set O(-mode) wal2
|
||||
set O(-tserver) "./tserver"
|
||||
set O(-seconds) 20
|
||||
set O(-writers) 1
|
||||
set O(-readers) 0
|
||||
set O(-integrity) 0
|
||||
set O(-verbose) 0
|
||||
|
||||
|
||||
proc error_out {err} {
|
||||
puts stderr $err
|
||||
exit -1
|
||||
}
|
||||
|
||||
proc usage {} {
|
||||
puts stderr "Usage: $::argv0 ?OPTIONS?"
|
||||
puts stderr ""
|
||||
puts stderr "Where OPTIONS are:"
|
||||
puts stderr " -database <database file> (default: test.db)"
|
||||
puts stderr " -mode <mode> (default: wal2)"
|
||||
puts stderr " -rows <number of rows> (default: 5000000)"
|
||||
puts stderr " -tserver <path to tserver executable> (default: ./tserver)"
|
||||
puts stderr " -seconds <time to run for in seconds> (default: 20)"
|
||||
puts stderr " -writers <number of writer clients> (default: 1)"
|
||||
puts stderr " -readers <number of reader clients> (default: 0)"
|
||||
puts stderr " -integrity <number of IC clients> (default: 0)"
|
||||
puts stderr " -verbose 0|1 (default: 0)"
|
||||
exit -1
|
||||
}
|
||||
|
||||
for {set i 0} {$i < [llength $argv]} {incr i} {
|
||||
set opt ""
|
||||
set arg [lindex $argv $i]
|
||||
set n [expr [string length $arg]-1]
|
||||
foreach k [array names ::O] {
|
||||
if {[string range $k 0 $n]==$arg} {
|
||||
if {$opt==""} {
|
||||
set opt $k
|
||||
} else {
|
||||
error_out "ambiguous option: $arg ($k or $opt)"
|
||||
}
|
||||
}
|
||||
}
|
||||
if {$opt==""} { usage }
|
||||
if {$i==[llength $argv]-1} {
|
||||
error_out "option requires an argument: $opt"
|
||||
}
|
||||
incr i
|
||||
set val [lindex $argv $i]
|
||||
switch -- $opt {
|
||||
-mode {
|
||||
if {$val != "wal" && $val != "wal2"} {
|
||||
set xyz "\"wal\" or \"wal2\""
|
||||
error_out "Found \"$val\" - expected $xyz"
|
||||
}
|
||||
}
|
||||
}
|
||||
set O($opt) [lindex $argv $i]
|
||||
}
|
||||
if {$O(-database)==""} {
|
||||
set O(-database) "test.db"
|
||||
}
|
||||
|
||||
set O(-rows) [expr $O(-rows)]
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Create and populate the required test database, if it is not already
|
||||
# present in the file-system.
|
||||
#
|
||||
proc create_test_database {} {
|
||||
global O
|
||||
|
||||
if {[file exists $O(-database)]} {
|
||||
sqlite3 db $O(-database)
|
||||
|
||||
# Check the schema looks Ok.
|
||||
set s [db one {
|
||||
SELECT group_concat(name||pk, '.') FROM pragma_table_info('t1');
|
||||
}]
|
||||
if {$s != "a1.b0.c0.d0"} {
|
||||
error_out "Database $O(-database) exists but is not usable (schema)"
|
||||
}
|
||||
|
||||
# Check that the row count matches.
|
||||
set n [db one { SELECT count(*) FROM t1 }]
|
||||
if {$n != $O(-rows)} {
|
||||
error_out "Database $O(-database) exists but is not usable (row-count)"
|
||||
}
|
||||
db close
|
||||
} else {
|
||||
catch { file delete -force $O(-database)-journal }
|
||||
catch { file delete -force $O(-database)-wal }
|
||||
|
||||
if {$O(-verbose)} {
|
||||
puts "Building database $O(-database)..."
|
||||
}
|
||||
|
||||
sqlite3 db $O(-database)
|
||||
db eval {
|
||||
CREATE TABLE t1(
|
||||
a INTEGER PRIMARY KEY,
|
||||
b BLOB(16),
|
||||
c BLOB(16),
|
||||
d BLOB(400)
|
||||
);
|
||||
CREATE INDEX i1 ON t1(b);
|
||||
CREATE INDEX i2 ON t1(c);
|
||||
|
||||
WITH s(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$O(-rows))
|
||||
INSERT INTO t1
|
||||
SELECT i-1, randomblob(16), randomblob(16), randomblob(400) FROM s;
|
||||
}
|
||||
db close
|
||||
}
|
||||
|
||||
switch -- $O(-mode) {
|
||||
wal {
|
||||
sqlite3 db $O(-database)
|
||||
db eval {PRAGMA journal_mode = delete}
|
||||
db eval {PRAGMA journal_mode = wal}
|
||||
db close
|
||||
}
|
||||
|
||||
wal2 {
|
||||
sqlite3 db $O(-database)
|
||||
db eval {PRAGMA journal_mode = delete}
|
||||
db eval {PRAGMA journal_mode = wal2}
|
||||
db close
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Functions to start and stop the tserver process:
|
||||
#
|
||||
# tserver_start
|
||||
# tserver_stop
|
||||
#
|
||||
set ::tserver {}
|
||||
proc tserver_start {} {
|
||||
global O
|
||||
set cmd "|$O(-tserver) -vfs unix "
|
||||
if {$O(-mode)=="wal2"} {
|
||||
append cmd " -walautocheckpoint 0 "
|
||||
}
|
||||
append cmd "$O(-database)"
|
||||
set ::tserver [open $cmd]
|
||||
fconfigure $::tserver -blocking 0
|
||||
fileevent $::tserver readable tserver_data
|
||||
}
|
||||
|
||||
proc tserver_data {} {
|
||||
global O
|
||||
if {[eof $::tserver]} {
|
||||
error_out "tserver has exited"
|
||||
}
|
||||
set line [gets $::tserver]
|
||||
if {$line != "" && $O(-verbose)} {
|
||||
puts "tserver: $line"
|
||||
}
|
||||
}
|
||||
|
||||
proc tserver_stop {} {
|
||||
close $::tserver
|
||||
set fd [socket localhost 9999]
|
||||
puts $fd ".stop"
|
||||
close $fd
|
||||
}
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
set ::nClient 0
|
||||
set ::client_output [list]
|
||||
|
||||
proc client_data {name fd} {
|
||||
global O
|
||||
if {[eof $fd]} {
|
||||
incr ::nClient -1
|
||||
close $fd
|
||||
return
|
||||
}
|
||||
set str [gets $fd]
|
||||
if {[string trim $str]!=""} {
|
||||
if {[string range $str 0 3]=="### "} {
|
||||
lappend ::client_output [concat [list name $name] [lrange $str 1 end]]
|
||||
}
|
||||
if {$O(-verbose)} {
|
||||
puts "$name: $str"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc client_launch {name script} {
|
||||
global O
|
||||
set fd [socket localhost 9999]
|
||||
fconfigure $fd -blocking 0
|
||||
puts $fd "PRAGMA synchronous = OFF;"
|
||||
puts $fd ".repeat 1"
|
||||
puts $fd ".run"
|
||||
puts $fd $script
|
||||
puts $fd ".seconds $O(-seconds)"
|
||||
puts $fd ".run"
|
||||
puts $fd ".quit"
|
||||
flush $fd
|
||||
incr ::nClient
|
||||
fileevent $fd readable [list client_data $name $fd]
|
||||
}
|
||||
|
||||
proc client_wait {} {
|
||||
while {$::nClient>0} {vwait ::nClient}
|
||||
}
|
||||
|
||||
proc script_writer {bCkpt} {
|
||||
global O
|
||||
|
||||
set commit ".mutex_commit"
|
||||
set begin "BEGIN CONCURRENT;"
|
||||
set ckpt ""
|
||||
if {$bCkpt} {
|
||||
set ckpt ".checkpoint 2000"
|
||||
}
|
||||
|
||||
set tail "randomblob(16), randomblob(16), randomblob(400));"
|
||||
return [subst -nocommands {
|
||||
$ckpt
|
||||
$begin
|
||||
REPLACE INTO t1 VALUES(abs(random() % $O(-rows)), $tail
|
||||
$commit
|
||||
}]
|
||||
}
|
||||
|
||||
proc script_reader {} {
|
||||
global O
|
||||
|
||||
return [subst -nocommands {
|
||||
BEGIN;
|
||||
SELECT * FROM t1 WHERE a>abs((random()%$O(-rows))) LIMIT 10;
|
||||
END;
|
||||
}]
|
||||
}
|
||||
|
||||
proc script_integrity {} {
|
||||
global O
|
||||
|
||||
return {
|
||||
.integrity_check
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
create_test_database
|
||||
tserver_start
|
||||
|
||||
for {set i 0} {$i < $O(-writers)} {incr i} {
|
||||
client_launch w.$i [script_writer [expr {$i==0 && $O(-mode)=="wal2"}]]
|
||||
}
|
||||
for {set i 0} {$i < $O(-readers)} {incr i} {
|
||||
client_launch r.$i [script_reader]
|
||||
}
|
||||
for {set i 0} {$i < $O(-integrity)} {incr i} {
|
||||
client_launch i.$i [script_integrity]
|
||||
}
|
||||
client_wait
|
||||
|
||||
set name(w) "Writers"
|
||||
set name(r) "Readers"
|
||||
set name(i) "Integrity"
|
||||
foreach r $::client_output {
|
||||
array set a $r
|
||||
set type [string range $a(name) 0 0]
|
||||
incr x($type.ok) $a(ok);
|
||||
incr x($type.busy) $a(busy);
|
||||
incr x($type.n) 1
|
||||
set t($type) 1
|
||||
}
|
||||
|
||||
foreach type [array names t] {
|
||||
set nTPS [expr $x($type.ok) / $O(-seconds)]
|
||||
set nC [expr $nTPS / $x($type.n)]
|
||||
set nTotal [expr $x($type.ok) + $x($type.busy)]
|
||||
set bp [format %.2f [expr $x($type.busy) * 100.0 / $nTotal]]
|
||||
puts "$name($type): $nTPS transactions/second ($nC per client) ($bp% busy)"
|
||||
}
|
||||
|
||||
tserver_stop
|
||||
|
||||
Reference in New Issue
Block a user